《深入理解ES6》阅读笔记 --- 类

《深入理解ES6》阅读笔记 --- 类

大部分面向对象的语言都支持类和类继承的特性

从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。这个继承不仅仅是原型上,也包括静态成员,而且就算父类与子类都有同样的方法名,也不怕被覆盖,可以用this.xxxsuper.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一样,需要搞那么多复杂的东西来模拟这个特性。

编辑于 2018-08-12

文章被以下专栏收录