类声明
cheat sheet:
类属性不可被赋予新值,在之前的实例中,PersonClass.prototype就是这样一个只可读的类属性;
类语法
类与ES5类型(new Person)之间有诸多相识之初,但也有差异:
- 函数声明可以被提升,而类声明与let声明类似,不能被提升;真正执行声明语句之前,它们会一直存在于临时死区
- 类声明中的所有代码将自动运行在严格模式下,而且无法强行让代码脱离严格模式执行;
- 在自定义类型中,需要通过Object.defineProperty方法手工指定某个方法不可枚举;而在类中,所有方法都是不可枚举的;
- 每个类都有一个名为[[Construct]]内部方法,通过关键字new调用那些不含有[[Construct]]的方法会导致程序抛出错误;
- 使用除关键字new以外的方法调用类的构造函数会导致程序抛出错误;
- 在类中修改类名会导致程序报错;
下面是ES5来实现上面例子类PersonClass的等价代码:
|
|
上述1和6可以看下面这个例子:
类表达式
声明表达式
|
|
命名表达式
|
|
在JS引擎中,类表达式的实现与类声明稍有不同。对于类声明来说,通过let定义的外部绑定与通过const定义的内部绑定具有相同的名称;而命名表达式通过const定义名称,从而PersonClass2只能在类的内部使用;
一等公民的类
在程序中,一等公民是指一个可以传入函数,可以从函数返回,并且可以复制给变量的值。JS函数是一等公民,ES6中把类也设计为一等公民。
作为参数传入函数:
|
|
立即调用类构造函数:
访问器属性
类也支持直接在原型上定义访问器属性:
可计算成员名称
类方法和访问器属性也支持使用可计算名称;
生成器方法
在类中可以在方法名称前附加一个星号来定义生成器;
|
|
静态成员
ES6中类简化了创建静态成员的工程,在方法或访问器属性前面使用正式的静态注释即可;
|
|
不可在实例中访问静态成员,必须要直接在类中访问静态成员
继承与派生
ES6中使用extends关键字可以创造继承类;
|
|
上述代码创造了一个Square继承Rectangle类;Square被称为派生类;注意:如果在派生类中指定了构造函数则必须要调用super,如果不这样做程序就会报错;如果不使用构造函数,则当创建新的类实例时会自动调用super并传入所有参数
|
|
使用super要注意:
- 只可在派生类的构造函数中使用super(),如果尝试在非派生类或者函数中使用会抛出错误
- 在构造函数中访问this前一定要调用super,它负责初试化this,如果在调用super之前尝试访问this会报错
- 如果不想调用super(),则唯一的方法是让类的构造函数返回一个对象
类方法遮蔽
派生类如果跟父类有同名函数,派生类的方法会覆盖父类的同名方法;如果想在派生类中调用父类的方法,可以super.funcName()
这样来调用;
静态成员继承
如果基类有静态成员,那么这些静态成员在派生类中也可调用。
新的静态方法create被添加到Rectangle中,继承后的Square.create()与Rectangle.create()行为类似;
派生自表达式的类
ES6最强大的一面或许是从表达式导出类的功能了;只要表达式可以被解析为一个函数并且具有[[Construct]]属性和原型,那么就可以用extends进行派生;extends强大的功能使得类可以继承自任意类型的表达式,从而可以创造更多可能性,例如动态的确定类的继承目标:
|
|
内建对象的继承
在ES6之前,几乎不可能通过继承方式创建属于自己的数组,如:
|
|
通过上面的代码,可以看出MyArray的行为与内建数组行为不一致;
ES6中可以通过extends来创建自定义数组;
在类的构造函数中使用new.target
类中new.targe一般就等于类本身:
|
|
但有时其值会不同:
|
|
Square调用Rectangle的构造函数,所以当调用发生时new.target等于Square;每个构造函数都可以根据自身被调用的方式改变自己的行为;例如构造一个抽象基类;
|
|
上例子中抽象基类Shape不能直接被实例化,只能通过派生的类来继承;