# JS 实现继承

JS 中可以通过多种方式来实现继承,我们来仔细分析一下。

# ES5 中的继承

ES5 中可以通过如下方式实现继承:

  • 原型链
    • 重写原型对象,代之以一个新的类型实例。
    • 问题:引用类型所有实例共享,无法向超类型传参,会影响其他子元素。
  • 借用构造函数
    • 子类型构造函数内部调用超类型构造函数,call,apply。
    • 问题:函数复用无从谈起
  • 组合继承
    • 使用原型链实现对原型属性和方法的继承,用构造函数类实现对实例属性的继承。
    • 问题:执行 2 次
  • 原型继承
    • Object.create()
    • 返回新对象,原型指向传入对象。
  • 寄生式继承
    • 在 Object.create()的基础上,增加自定义属性和方法来增强对象。
  • 寄生组合式继承
    • es5 中完美解决方案
function inherit(subType, superType) {
  var prototype = Object.create(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}

function SuperType(age) {
  this.age = age;
}

function SubType(age) {
  SuperType.call(this, age);
}

inherit(SubType, SuperType);

var instance = new SubType(123);
var instance2 = new SubType(456);

console.log(instance.age);
console.log(instance2.age);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# ES6 中的继承

ES6 采用 class extends 进行继承。和 ES5 继承的区别:

  • ES5 的继承实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面。
  • ES6 的继承实质是先创造父类的实例对象 this,然后再用子类的构造函数修改 this。

# super

使用 super 的时候,必须显示指定是作为函数还是作为对象使用,否则会报错。

super 调用父类的方法时,super 会绑定子类的 this。

# proto属性

每一个对象都有proto属性,指向对应的构造函数的 prototype 属性。

子类的proto属性表示构造函数的继承,总是指向父类。

子类 prototype 的proto属性表示方法的继承,总是指向父类的 prototype 属性。

class A {}
class B extends A {}

console.log(B.__proto__ === A); // true
console.log(B.prototype.__proto__ === A.prototype); // true
1
2
3
4
5

# 继承目标

只要是一个有 prototype 属性的函数,就能被继承。

根据 ES5 和 ES6 继承的区别,可以得出如下结论:

ES5 无法继承内置构造函数,Boolean,Number,String,Array,Date,Function,RegExp,Error,Object。

ES6 可以,但继承 Object 不行,(特例,因为无法向父类 object 传参)。