很多面向对象语言都支持两种继承:接口继承和实现继承, 很多面向对象语言都支持两种继承:接口继承和实现继承, ECMAScript 中是不可能的,因为函数没有签名。实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。

原型链

通过原型继承多个引用类型的属性和方法。

  1. function SuperType() {
  2. this.property = true;
  3. }
  4. SuperType.prototype.getSuperValue = function() {
  5. return this.property;
  6. };
  7. function SubType() {
  8. this.subproperty = false;
  9. }
  10. // 继承 SuperType
  11. SubType.prototype = new SuperType();
  12. SubType.prototype.getSubValue = function () {
  13. return this.subproperty;
  14. };
  15. let instance = new SubType();
  16. console.log(instance.getSuperValue()); // true
  • SubType.prototype 现在是 SuperType 的一个实例
  • SubType.prototype 的 constructor 属性被重写为指向 SuperType,所以 instance.constructor 也指向 SuperType。
  • 调用 instance.getSuperValue()经过了 3 步搜索:instance、 SubType.prototype 和 SuperType.prototype,最后一步才找到这个方法。对属性和方法的搜索会 一直持续到原型链的末端 。

    默认原型

  • 所有引用类型都继承自 Object,也是通过原型链实现的

    原型链与继承关系

  • 使用 instanceof 操作符,如果是当前实例就返回true

  • isPrototypeOf() 只要原型链中包含这个原型,这个方法就返回 true ;

    1. Object.prototype.isPrototypeOf(instance); // true

    原型链上的方法

  • 在继承构造函数上的方法,如果和父构造函数同名,会覆盖父构造函数上的方法

  • 注意:如果是重写了prototype,继承就失效了

    1. // 通过对象字面量添加新方法,这会导致上一行无效
    2. SubType.prototype = {
    3. getSubValue() {
    4. return this.subproperty;
    5. },
    6. someOtherMethod() {
    7. return false;
    8. }
    9. };

    原型链的问题

  • 原型中包含的引用值会在所有实例间共享,这也是为什么属性通常会 在构造函数中定义而不会定义在原型上的原因。

  • 子类型在实例化时不能给父类型的构造函数传参

    盗用构造函数

    有时也称作“对象伪装”或“经典继承”

  1. function SuperType(name){
  2. this.name = name;
  3. }
  4. function SubType() {
  5. // 继承 SuperType 并传参
  6. SuperType.call(this, "Nicholas");
  7. // 实例属性
  8. this.age = 29;
  9. }
  10. let instance = new SubType();
  11. console.log(instance.name); // "Nicholas";
  12. console.log(instance.age); // 29
  • 盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传参。
  • 主要缺点: 必须在构造函数中定义方法,函数不能重用,子类也不能访问父类原型上定义的方法。

    组合继承

    也叫伪经典继承, 基本思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。 组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力

  1. function SuperType(name){
  2. this.name = name;
  3. this.colors = ["red", "blue", "green"];
  4. }
  5. SuperType.prototype.sayName = function() {
  6. console.log(this.name);
  7. };
  8. function SubType(name, age){
  9. // 继承属性
  10. SuperType.call(this, name);
  11. this.age = age;
  12. }
  13. // 继承方法
  14. SubType.prototype = new SuperType();
  15. SubType.prototype.sayAge = function() {
  16. console.log(this.age);
  17. };
  18. let instance1 = new SubType("Nicholas", 29);
  19. instance1.colors.push("black");
  20. console.log(instance1.colors); // "red,blue,green,black"
  21. instance1.sayName(); // "Nicholas";
  22. instance1.sayAge(); // 29
  23. let instance2 = new SubType("Greg", 27);
  24. console.log(instance2.colors); // "red,blue,green"
  25. instance2.sayName(); // "Greg";
  26. instance2.sayAge(); // 27

原型式继承

  1. function object(o) {
  2. function F() {}
  3. F.prototype = o;
  4. return new F();
  5. }
  6. let person = {
  7. name: "Nicholas",
  8. friends: ["Shelby", "Court", "Van"]
  9. };
  10. let anotherPerson = object(person);
  11. anotherPerson.name = "Greg";
  12. anotherPerson.friends.push("Rob");
  13. let yetAnotherPerson = object(person);
  14. yetAnotherPerson.name = "Linda";
  15. yetAnotherPerson.friends.push("Barbie");
  16. console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
  • 使用情况: 你有一个对象,想在它的基础上再创建一个新对象。 你需要把这个对象先传给 object(),然后再对返回的对象进行适当修改 。
  • Object.create() — 等同于这个方法,但create方法可以传入第二个参数,与definpropety的第二个参数一致

    1. let person = {
    2. name: "Nicholas",
    3. friends: ["Shelby", "Court", "Van"]
    4. };
    5. let anotherPerson = Object.create(person, {
    6. name: {
    7. value: "Greg"
    8. }
    9. });
    10. console.log(anotherPerson.name); // "Greg"
  • 原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住, 属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。

    寄生式继承

    1. function createAnother(original){
    2. let clone = object(original); // 通过调用函数创建一个新对象
    3. clone.sayHi = function() { // 以某种方式增强这个对象
    4. console.log("hi");
    5. };
    6. return clone; // 返回这个对象
    7. }
  • 通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。

寄生式组合继承

  • 组合继承其实也存在效率问题。最主要的效率问题就是父类构造函数始终会被调用两次:一次在是 创建子类原型时调用,另一次是在子类构造函数中调用。

    1. function SuperType(name) {
    2. this.name = name;
    3. this.colors = ["red", "blue", "green"];
    4. }
    5. SuperType.prototype.sayName = function() {
    6. console.log(this.name);
    7. };
    8. function SubType(name, age){
    9. SuperType.call(this, name); // 第二次调用 SuperType()
    10. this.age = age;
    11. }
    12. SubType.prototype = new SuperType(); // 第一次调用 SuperType()
    13. SubType.prototype.constructor = SubType;
    14. SubType.prototype.sayAge = function() {
    15. console.log(this.age);
    16. };
  • 实现原理

    1. function SuperType(name) {
    2. this.name = name;
    3. this.colors = ["red", "blue", "green"];
    4. }
    5. SuperType.prototype.sayName = function() {
    6. console.log(this.name);
    7. };
    8. function SubType(name, age){
    9. SuperType.call(this, name); // 第二次调用 SuperType()
    10. this.age = age;
    11. }
    12. SubType.prototype = new SuperType(); // 第一次调用 SuperType()
    13. SubType.prototype.constructor = SubType;
    14. SubType.prototype.sayAge = function() {
    15. console.log(this.age);
    16. };
  • 寄生式组合继承可以算是引用类型继承的最佳模式。