欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ECMAScript 6 - Class的继承

程序员文章站 2022-06-13 15:52:11
...

基本用法

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多

class Test extends Component {}

上面代码定义了一个Test类,该类通过extends关键字,继承了Component类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Component类。下面,我们在Test内部加上代码。

class Test extends Component {
  constructor(props) {
    super(props); // 调用父类constructor(props)
  }
  
  toTest() {
    super.toTest(); // 调用父类toTest()
  }
}

上面代码中,constructor方法和toTest方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象

class Component {}

class Test extends Component {
  cosntructor() {
    
  }
}

let a = new Test();

上面代码中,Test继承了父类Component,但是它的构造函数没有调用super方法,导致新建实例时报错。

ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this

如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

constructor(...args) {
  super(...args);
}

另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

class Component {
  constructor(props) {
    this.props = props;
  }
}

class Test extends Component {
  cosntructor(props, a) {
    this.a = a; // ReferenceError
    super(props);
    this.a = a; // 正确
  }
}

上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

类的prototype属性和proto属性

大多数浏览器的ES5实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

  • 子类的__proto__属性,表示构造函数的继承,总是指向父类
  • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
class A { }

class B extends A { }

B.__proto__ === A;  // true
B.prototype.__proto__ === A.prototype;  // true

上面代码中,子类B__proto__属性指向父类A,子类Bprototype属性的__proto__属性指向父类Aprototype属性。

Extends的继承目标

extends关键字后面可以跟多种类型的值

class B extends A {}

上面代码的A,只要是一个有prototype属性的函数,就能被B继承。由于函数都有prototype属性(除了Function.prototype函数),因此A可以是任意函数。

下面,讨论三种特殊情况。

第一种特殊情况,子类继承Object类。

class A extends Object {
  
}

A.__proto__ === Object;  // true
A.prototype.__proto__ === Object.prototype;  // true

这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。

第二种特殊情况,不存在任何继承。

class A {}

A.__proto__ === Function.prototype;  // true
A.prototype.__proto__ === Object.prototype;  // true

这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。

第三种特殊情况,子类继承null

class A extends null {
  
}

A.__proto__ === Function.prototype;  // true
A.prototype.__proto__ === undefined;  // true

这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回的对象不继承任何方法,所以它的__proto__指向Function.prototype,即实质上执行了下面的代码。

class C extends null {
  constructor() {
    return Object.create(null);
  }
}

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

Object.getPrototypeOf(A) === B;

因此,可以使用这个方法判断,一个类是否继承了另一个类。

super关键字

super这个关键字,有两种用法,含义不同。

  • 作为函数调用时(即super(...args)),super代表父类的构造函数
  • 作为对象调用时(即super.propsuper.method()),super代表父类。注意,此时super即可以引用父类实例的属性和方法,也可以引用父类的静态方法
class B extends A {
  get m() {
    return this._p * super._p;
  }
  
  set m() {
    throw new Error('该属性只读');
  }
}

上面代码中,子类通过super关键字,调用父类实例的_p属性。

由于,对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字

var obj = {
  toString() {
    return "MyObject: " + super.toString();
  }
};

obj.toString(); // MyObject: [object Object]

生命不息,折腾不止...
I'm not a real coder, but i love it so much!