继承以及Super

一个小小的总结,主要关注以下三个问题:ES5的继承方式,ES5的继承与ES6的继承的区别,ES6的super的几种使用方式以及其中this的指向。

From https://LuckyMona.github.io

一、ES5的继承

JS实现继承的几种方式

MDN | Object.create | 用 Object.create实现类式继承

继承可以分为对象实例的继承,类的继承

二、ES6的继承

Class B extends A { } 中的A可以是个class,还可以是个有prototype属性的函数

三、ES5继承与ES6继承的区别

  1. this的区别

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

  2. 原型链ES6有两条

    1
    2
    3
    4
    5
    class A { }
    class B extends A { }

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

    这样的结果是因为,类的继承是按照下面的模式实现的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A { }
    class B { }
    // B 的实例继承 A 的实例
    Object.setPrototypeOf(B.prototype, A.prototype);
    // B 继承 A 的静态属性
    Object.setPrototypeOf(B, A);
    const b = new B();

    //setPrototypeOf的内部实现
    Object.setPrototypeOf = function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
    }
    //但是setPrototypeOf会有性能问题,通常推荐使用Object.create
  3. ES6可以继承原生构造函数,而ES5不能

    1. 原生构造函数有:Boolean()、Number()、String()、Array()、Date()、Function()、RegExp()、Error()、Object()

    2. 原因:

      • ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。

      • ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。

    3. 继承Object的子类,有一个行为差异

      1
      2
      3
      4
      5
      6
      7
      class NewObj extends Object{
      constructor(){
      super(...arguments);
      }
      }
      var o = new NewObj({attr: true});
      o.attr === true // false

      上面代码中,NewObj继承了Object,但是无法通过super方法向父类Object传参。这是因为 ES6 改变了Object构造函数的行为,一旦发现Object方法不是通过new Object()这种形式调用,ES6 规定Object构造函数会忽略参数。

四、ES6的super及其this

  1. 作为函数:在子类构造函数中调用 super() ,super相当于父类constructor

  2. 作为对象

    • 在子类构造函数或普通函数中使用 super.methodA() ,super相当于父类原型

      此时methodA中的this指向子类实例

    • 在子类静态方法中使用 super.methodB(),super指向父类而非原型,此时的methodB指的是父类的静态方法methodB

      此时methodB中的this指向子类,所以只能通过this访问到子类的静态方法和属性

  3. 在子类中使用super给属性赋值 super.father_prop = 1 ,相当于子类的this this.father_prop=1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class A {
    constructor() {
    this.x = 1;
    }
    }

    class B extends A {
    constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
    }
    change(){
    super.x = 4
    console.log(super.x); //undefined
    console.log(this.x); // 4
    }
    }
    let b = new B();
    b.change()
  4. 使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。

    1
    2
    3
    4
    5
    6
    7
    8
    class A {}

    class B extends A {
    constructor() {
    super();
    console.log(super); // 报错
    }
    }

五、ES6 的class的本质

  1. 不存在任何继承时。A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Function.prototype

    1
    2
    3
    class A { }
    A.__proto__ === Function.prototype // true
    A.prototype.__proto__ === Object.prototype // true

参考

  1. Class 的继承