es6之前js都不支持类和类的继承,直到es6引入了类的特性。
js语言的传统方法通过构造函数定义并生成新对象。
function Person (name) { this.name = name; } Person.prototype.sayName = function() { console.log(this.name); } let person = new Person('xiaoqi'); person.sayName();// 'xiaoqi' console.log(person instanceof Person);// true console.log(person instanceof Object); // true这段代码Person是一个构造函数,给其创建一个name属性,给Person的原型添加一个sayName()方法, 所有Person实例共享这个方法。使用new操作符创建person实例,由于存在原型继承的特性,所以person也是Object的实例。
es6引入了Class这个概念作为对象的模板,es6中的class可以看做只是一个语法糖,它的绝大部分的功能,ES5都能做到,新的class写法只是让对象原型的写法更清晰。
上面的例子改写为“类”的形式:
class Person { // 等价于Person 构造器 constructor (name) { this.name = name; } // 等价于Person.prototype.sayName sayName() { console.log(this.name); } } let person = new Person('xiaoqi'); person.sayName();// 'xiaoqi' console.log(person instanceof Person);// true console.log(person instanceof Object);// true console.log(typeof Person);// function console.log(person.constructor === Person);// true console.log(typeof Person.prototype.sayName);// function console.log(Person === Person.prototype.constructor);// true 通过类声明的Person与创建构造函数Person的过程相似,只是在类中通过特殊的constructor方法名来定义构造函数,不需要加function这个保留字。方法之间不需要逗号分隔,加了会报错。私有属性是实例中的属性,不会出现在原型上,且只能在类的构造函数或方法中创建,上例中name就是一个私有属性。es6的类完全可以看作构造函数的另一种写法,类本身就指向构造函数。并且构造函数的prototype属性在es6上继续存在,类的所有方法(除constructor以外)都定义在类的prototype属性上。[注意]: 与函数不同的是,类属性不可被赋予新值,Person.prototype就是一个只读类属性
es6的类与es5的构造函数之间的差异:
类的内部定义的原型上的所有方法都是不可枚举的类和模块内部默认使用严格模式,不需要使用use strict指定运行模式constructor方法是类的默认方法,通过new命令生成的对象实例时自动调用该方法。一个类必须有constructor方法,如果没有显示的定义,一个空的constructor方法会被默认添加。 class Person { } // 等同于 class Point { constructor() {} }constructor方法默认返回实例对象(即this),不过完全可以像es5那样,返回一个新的对象
class Foo{ constructor() { return Object.create(null) } } var foo = new Foo(); console.log(foo.constructor === Foo);// false console.log(foo.constructor);// undefinedconstructor函数返回了一个新的对象,导致实例对象不是Foo的实例
类必须使用new操作符来调用,否则会报错
类不存在变量提升,这与es5完全不同
new Foo() // ReferenceError class Foo {}es6的类只是es5构造函数的一层包装,所以函数的许多特性都被从class继承,例如name属性
class Point {} Point.name // Pointname属性总是返回紧跟在class关键字后面的类名
与函数一样,class也可以使用表达式的形式定义:
const MyClass = class Me { getClassName () { return Me.name } } let inst = new MyClass(); console.log(inst.getClassName());// Me console.log(MyClass.name);// Me这个类的名字是MyClass,而不是Me,Me只是在class的内部代码可用,指代当前类。
如果class内部没有用到,那么可以省略Me,也可以写成下面的形式:
const MyClass = class{ }采用Class表达式,可以得到立即执行的class实例:
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }('xiaoqi'); person.sayName();// 'xiaoqi'私有方法是常见的需求,但es6不提供,只能通过变通的方法来模拟实现
方法1: 在命名上加以区别
class Widget { // 共有方法 foo(baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } }_bar方法前面的下划线表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部还是可以调用这个方法
方法2:将私有方法移除模块,因为模块内的所有方法都是对外可见的
class Widget { foo (baz) { bar.call(this, baz); } } function bar (baz) { return this.snaf = baz; } let instance = new Widget(); instance.foo('xiao'); console.log(instance.snaf); // 'xiao'foo是共有的方法,内部调用bar.call(this, baz),使得bar实际上成为当前模块的私有方法
方法3: 利用Symbol值的唯一性将私有方法的名字命名为一个Symbol值
const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class myClass { // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } }上面的代码中,bar和sanf都是Symbol值,导致第三方无法获取它们,因此达到私有方法与私有属性的效果。
与私有方法一样,es6不支持私有属性。目前,有一个提案为给class加私有属性。方法是在属性名前面使用#来表示
class Point { #x; constructor (x = 0) { #x = +x; } get x() { return #x; } set x(value) { #x = +value } }#x表示私有属性x,在Point类之外是读取不到这个属性的。
该提案只规定了私有属性的写法。但是很自然的,它也可以用来编写私有方法
class Foo{ #a; #b; #sum() { return #a + #b; } printSum() { console.log(#sum()); } constructor(a,b) { #a = a; #b = b; } }与es5一样,在类的内部可以使用get和set关键字对某个属性设置存值函数和取值函数
class CustomHTMLElement { constructor(element) { this.element = element; } get html() { return this.element.innerHTML; } set html(value) { this.element.innerHTML = value; } } var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, 'html'); console.log('get' in descriptor); // true console.log('set' in descriptor); // true console.log(descriptor.enumerable);// false这段代码中的CustomHTMLElement类是一个针对现有DOM元素的包装器,并通过getter和setter方法将这个元素的innerHTML方法委托给HTML属性,这个访问器属性是在CustomHTMLElement.prototypr上创建的。与其他方法一样,这两个方法都是不可枚举的。
在某个方法之前加上星号(*),就表示该方法是一个Generator函数
class MyClass { *createIterator() { yield 1; yield 2; yield 3; } } let instance = new MyClass(); let iterator = instance.createIterator(); console.log( iterator.next());// {value: 1, done: false}这段代码创建一个MyClass的类,它有一个生成器方法createIterator(),其返回值为一个迭代器。
尽管生成器方法很实用,但如果类是用来表示值的集合的,那么为它定义一个默认迭代器会更有用。
通过Symbol.iterator定义生成器方法即可为类定义默认的迭代器。
class Collection { constructor() { this.items = []; } *[Symbol.iterator]() { yield *this.items.values(); } } var collection = new Collection(); collection.items.push(1); collection.items.push(2); collection.items.push(3); for(let x of collection) { // 1 // 2 // 3 console.log(x); }上面的代码 Collection类的Symbol.iterator方法返回一个默认的遍历器。任何管理一系列值的类都应该引入默认的迭代器,因为一些与特定集合有关的操作需要所操作的集合含有一个迭代器。现在可以将collection实例直接用于for-of循环中或用展开运算符操作它。
[es5中,模拟静态方法]
function Person(name) { this.name = name; } // 静态方法 Person.create = function (name) { console.log('xiao'); } // 实例方法 Person.prototype.sayName = function() { console.log(this.name); } Person.create();// 'xiao'[class的静态方法]
所有在类中定义的方法都会被实例继承。如果在一个方法前加上static关键字,就表示方法不会被实例继承,而是通过类调用,称为“静态方法”
class PersonClass { // 等价于构造器 constructor (name) { this.name = name; } sayName() { console.log(this.name); } static create(name) { return new PersonClass(name); } } let person = PersonClass.create('xiaoqi'); console.log(person.name);// 'xiaoqi'静态方法在类上直接调用,不能再实例上调用
[class静态属性和实例属性]
实例属性:可以用等式直接写入类的定义中.
静态属性: 在实例属性上加static关键字.
class MyClass { myProp = 42; static myStaticProp = 43; constructor() { console.log(this.myProp);// 42 console.log(MyClass.myStaticProp);//43 } } let instance = new MyClass(); }new.target这个属性可以用来确定构造函数是怎么调用的。
在构造函数中返回new命令所作用的构造函数。如果构造函数不是通过new命令调用的,那么new.target会返回undefined。
function Person(name) { if(new.target !== undefined) { // 或者 new.target === Person this.name = name }else { throw new Error('必须使用new生成实例') } } var person = new Person('xiaoqi'); //var notPerson = Person.call(person, 'xiaoqi');// Uncaught Error: 必须使用new生成实例子类继承父类时new.target会返回子类:
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle) ; } } class Square extends Rectangle { constructor(length) { super(length, length); } } let obj = new Square(3);// false利用这个特点写出不能独立使用而必须继承之后才能使用的类:
class Shape { constructor () { if(new.target === Shape){ throw new Error('本类不能实例化'); } } } class Rectangle extends Shape { constructor (length, width){ super(); } } let x = new Shape();// Uncaught Error: 本类不能实例化 let y = new Rectangle(4,4);