大部分面向对象的语言都支持类和类继承的特性
从ECMA1-ECMA5的版本都不支持类和类继承的特性,于是开发者们通过原型,构造函数等来模拟类和类继承特性,这里不在复述,如果你有兴趣的话,可以阅读一下红包书(JavaScript高级程序设计)中关于类,类继承这两章。ECMA6终于至少在语言层(依然是基于原型的语法糖)面看起来支持了类和类继承,理解类的基本原理有助于理解ES6关于类的设计。
基本的类声明
class Human{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name)
}
}
let man = new Human('icepy')
man.sayName()
console.log(typeof Human) // function
类的基本定义与一个构造函数非常的类似,通过typeof也能看得出来class定义的也是一个function,在声明中,我们定义的属性,定义了原型上的方法sayName。但是,类声明的比之函数声明,又多了一些不同的东西,比如类声明是不允许提升的,且内部运行的环境100%的严格模式不允许强制降级。
类与函数一样,也可以是表达式,也可以将类当一个参数传给另外的函数或者类:
let Human = class {
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name)
}
}
function fetchObj(HumanClass){
return new HumanClass('icepy')
}
let man = fetchObj(Human)
但是匿名的类表达式,有一个不好的地方,就是在调试的时候很难定位,不过我们可以像函数一样给表达式加上一个name:
let Human = class Human{
...
}
高阶知识
我们可以为类中的属性创建访问器,就像使用Object.defineProperty给对象的属性创建访问器一样的含义。
class Human{
constructor(name){
this.name = name;
}
get name(){
return this.manName;
}
set name(newValue){
this.manName = newValue;
}
}
唯一需要注意的是关于“死循环”的问题。
类中的方法,访问器,都可以和对象一样,可计算,也就是使用[]这些方法,属性的名字可以用变量来代替。
有趣的知识点,是可以在类中声明一个generator方法,这一点倒是可以和上一小节的内容结合起来,不过一般情况下,在类中声明一个generator方法的情况非常少见:
class Human{
constructor(name){
this.name = name;
}
*sayName(){
yield 1;
}
}
你如果有兴趣的话,可以把sayName改造成可支持异步的。
关于“静态”也就是说不必实例化就可以调用的方法,类语法也支持了这个:
class Human{
constructor(name){
this.name = name;
}
static sayWork(name){
return new Human(name)
}
}
Human.sayWork()
有趣的是,如果是在同一个类中两个静态方法,其中一个方法想调用另外一个静态方法,这个时候,也可以使用this。
class Human{
constructor(name){
this.name = name
}
static sayWork(name){
return new Human(this.whichName())
}
static whichName(){
return 'icepy'
}
}
既然说到了类,我们不可避免的要谈到继承,在ES6中的类继承几乎简化到了一个关键字 extends,而且从前面的可计算来说,继承这个地方也是可以被计算的。
class Human{
constructor(name){
this.name = name;
}
}
class Icepy extends Human{
constructor(name){
super(name)
}
sayName(){
console.log(this.name)
}
}
关于super使用的时机,在前几章中有谈到,类中如果有继承,并且指定了constructor,那么就必须在使用this之前先调用super来初始化this。这个继承不仅仅是原型上,也包括静态成员,而且就算父类与子类都有同样的方法名,也不怕被覆盖,可以用http://this.xxx和 http://super.xxx来分别调用,在以往我们用原型模拟时,就非常难界定这个方法到底调用来自哪里。
如果你想知道类是否被实例化,也可以通过new.target来确定,在别的语言中有抽象类的概念,也就是只定义描述不搞实现,并且不能被实例化,只能被继承。这个时候,new.target就能排上用场了。
class Human{
constructor(){
if (new.target === Human){
throw new Error('抽象类不可以使用new')
}
}
sayName(){}
}
最后一个想说一下的是关于Symbol.species属性,这个属性用来返回函数的静态访问器属,说得更直白一些,你想用instanceof来判断一个对象属于哪个类,用它就能改变这个逻辑的内部实现,从而判定出可能不是原来访问器的类名。
class MyArrar extends Array{
static get [Symbol.species](){
return Array
}
}
正常情况下MyArray返回的必然是MyArray,如果我想让它返回Array,那么这个时候就需要用到Symbol.species属性了。
类属于ES6的新特性,它让我们可以更方便,安全的定义类,使用类,而不是想ES5一样,需要搞那么多复杂的东西来模拟这个特性。
更多精彩内容可关注我的个人微信公众号:搜索fed-talk