ECMAScript 6(25)class继承

1. class 继承

1.1 什么是继承

  1. 就是定义两个类,一个类中包含了另一个类中大多数的方法和属性,那么定义两个方法和属性重复度高的类就有点浪费.
  2. 于是通过继承 把 A 类的方法继承给 B,那么 B 就可以使用 A 类的方法和属性,这就是继承.B 继承 A,A 就叫父类,B 子类.
  3. es5 是如何实现继承的, 定义两个构造函数, 通过在子类中使用 apply 调用父类,重新定义父类 this 指向实现属性的继承,然后子类的 prototype = 父类的 prototype 实现方法的继承,但是这样就重写了子类的 prototype,子类要添加方法必须在这一步后面才行,这也是 es5 继承的不足之处.
  4. es6 直接通过 extends 关键字实现, 语法更简单直接.

1.2 class 继承 (extends)

子类 extends 父类

class Point {
}

class ColorPoint extends Point {
}

1.3 constructor

  • class 中,子类如果不写 constructor 方法,默认继承父类 constructor
class ColorPoint extends Point {
}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
  • 但是如果写了 constructor 方法,就必须调用 super 方法,否则新建实例时会报错.
  • 原因 : 这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。
  • 在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有 super 方法才能调用父类实例。
class ColorPoint extends Point {
  constructor(x, y, color) {
    // this.color = color; 这里会报错
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}
  • ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),然后再用子类的构造函数修改 this。
  • es5 先构建子类后继承父类,而后在改造, es6 先继承父类后构建子类,而后在改造

1.4 父类和子类的关系

  • es6 继承满足这两个条件
  1. 父类的方法和属性被子类继承
  2. 父类的静态方法,被子类继承。
class A {
    a() {
    }
    static aa(){
      console.log('aa')
    }
}

class B extends A {
    b() {
    }
}
var b = new B()
// 父类的静态方法,被子类继承。
B.aa() // aa
b.aa() // Uncaught TypeError: b.aa is not a function
//  说明 B.__proto__ === A
// 当构造函数通过new来生成实例时,构造函数的prototype属性将被自动转为__proto__作为实例的原型链(只有对象在执行方法时,会顺着__proto__去找对象本身没有的方法)
// 类的方法位于类的prototype属性上
// 所以 B.prototype === A.prototype;  // false
// 为什么是false? 因为这是es5的实现方法,父类先直接覆盖子类的prototype,后写入子类方法(如果后覆盖会把先写入的子类方法覆盖掉)
// 那怎么办 我们打印 B.prototype 属性,发现里面有一个__proto__属性,原来es6是把父类prototype属性挂在子类的prototype.__proto__上面了,对象在调用方法时,如果找不到对应的方法,那么会沿着__proto__属性一直往上找。
// 所以有 B.prototype.__proto__ === A.prototype;  // true

es6 的两个满足条件就是 :

B.proto === A // true 静态方法继承
B.prototype.proto === A.prototype; // true 属性方法继承

1.5 Object.getPrototypeOf()

  • Object.getPrototypeOf 方法可以用来从子类上获取父类。
Object.getPrototypeOf(B) === A // true
  • 可以使用这个方法判断,一个类是否继承了另一个类。

1.6 不使用 extends 怎么实现继承

  • 不使用 extends 怎么实现继承
function A(){
  ...
}
A.staticA = function(){}
A.prototype.A = function(){}

class B {
    constructor() {
        A.prototype.constructor.call(this) // 关键
    }
    b() {
        console.log('bar')
    }
}
// 核心:两条原型链
B.__proto__ = A
B.prototype.__proto__ = A.prototype
// 然后就完成了

2. super

super 这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

  • 第一种情况,作为函数调用时,代表父类的构造函数。
  1. 只能用在子类的构造函数中
  2. this 之前调用
  3. 必须执行一次
    不满足上面情况就会报错
class A {}

class B extends A {
  constructor() {
    super();  // super()在这里相当于A.prototype.constructor.call(this)。
  }
}
  • 第二种情况,作为对象时
  1. 普通方法中,指向父类的原型对象;
class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();
  1. 在静态方法中,指向父类。
class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2
  1. super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined
  1. 如果属性定义在父类的原型对象上,super 就可以取到。
class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }
}

let b = new B();
  1. 由于 this 指向子类实例,所以如果通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性。
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
  }
}

let b = new B();
  • 6.在子类的静态方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,
class A {
  constructor() {
    this.x = 1;
  }
  static print() {
    console.log(this.x);
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  static m() {
    super.print();
  }
}

B.x = 3;
B.m() // 3
  1. super 作为对象使用时,只能 super.xx 不是直接打印 super,会报错

3. class 语法糖

  • 其实上面已经说明了
  • 每一个对象都有proto属性,指向对应的构造函数的 prototype 属性。Class 作为构造函数的语法糖,同时有 prototype 属性和proto属性,因此同时存在两条继承链。
  • 当构造函数通过 new 来生成实例时,构造函数的 prototype 属性将被自动转为proto作为实例的原型链
  1. 子类的proto属性,表示构造函数的继承,总是指向父类。
  2. 子类 prototype 属性的proto属性,表示方法的继承,总是指向父类的 prototype 属性。
class A {
}

class B extends A {
}

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

4. 原生构造函数的继承

  • 继承 Array
class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined
  • 继承 Object
class NewObj extends Object{
  constructor(){
    super(...arguments);
  }
}
var o = new NewObj({attr: true});
o.attr === true  // false

等等… 更多继承参考阮一峰 es6

5. Mixin

  • 就是实现多个类继承
  • 阮一峰的实现
function mix(...mixins) {
  class Mix {
    constructor() {
      for (let mixin of mixins) {
        copyProperties(this, new mixin()); // 拷贝实例属性
      }
    }
  }

  for (let mixin of mixins) {
    copyProperties(Mix, mixin); // 拷贝静态属性
    copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
  }

  return Mix;
}

function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== 'constructor'
      && key !== 'prototype'
      && key !== 'name'
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(target, key, desc);
    }
  }
}

参考博文 阮一峰 es6

你可能感兴趣的:(ES6系列)