很多面向对象语言都支持接口继承和实现继承。前者只继承方法签名,后者继承实际的方法。
ECMAScript 中的函数没有签名,所以只支持实现继承,并且通过原型链实现。
原型链
原型链的基本思想是通过原型继承多个引用类型的属性和方法,在实例和原型之间构造一条原型链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function SuperType() { this.property = true; }
SuperType.prototype.getSuperValue = function () { return this.property; };
function SubType() { this.subproperty = false; }
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () { return this.subproperty; };
let instance = new SubType(); console.log(instance.getSuperValue());
|

在读取实例上的属性时,首先在实例上搜索这个属性。如果没找到,则会继承搜索实例的原型
通过原型链实现继承后,可以继承向上搜索,搜索原型的原型
对属性和方法的搜索会一直持续到原型链的末端
调用 instance.getSuperValue() 的搜索过程:
instance -> SubType.prototype -> SuperType.prototype -> SuperType.getSuperValue()
默认原型
默认情况下,所有引用类型都继承自 Object

调用 instance.toString() 时,实际上调用的是保存在 Object.prototype 上的方法
原型与继承关系
接前面代码:
1 2 3 4 5 6 7
| console.log(instance instanceof Object); console.log(instance instanceof SuperType); console.log(instance instanceof SubType);
console.log(Object.prototype.isPrototypeOf(instance)); console.log(SuperType.prototype.isPrototypeOf(instance)); console.log(SubType.prototype.isPrototypeOf(instance));
|
方法
子类覆盖父类的方法,或者增加父类没有的方法必须在原型赋值之后再加到原型上
以对象字面量方式创建原型方法会破坏之前的原型链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| function SuperType() { this.property = true; }
SuperType.prototype.getSuperValue = function() { return this.property; };
function SubType() { this.subproperty = false; }
SubType.prototype = new SuperType();
SubType.prototype = { getSubValue() { return this.subproperty; },
someOtherMethod() { return false; } };
let instance = new SubType();
console.log(instance.getSuperValue());
|
以对象字面量覆盖后的原型是一个 Object 的实例,不再是 SuperType 的实例。之前的原型链断开,SubType 和 SuperType 之间没关系了
原型链的问题
盗用构造函数
为解决原型包含引用值导致的继承问题,在子类构造函数中调用父类构造函数,使用 apply() 和 call() 方法以新创建的对象为上下文执行构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function SuperType() { this.colors = ["red", "blue", "green"]; }
function SubType() {
SuperType.call(this); }
let instance1 = new SubType(); instance1.colors.push("black");
console.log(instance1.colors);
let instance2 = new SubType();
console.log(instance2.colors);
|
传递参数
盗用构造函数可以在子类构造函数中向父类构造函数传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function SuperType(name) { this.name = name; }
function SubType() { SuperType.call(this, "Nicholas");
this.age = 29; }
let instance = new SubType(); console.log(instance.name); console.log(instance.age);
|
盗用构造函数的问题
组合继承
组合继承综合了原型链和盗用构造函数,使用原型链继承原型上的属性和方法(共享原型方法),通过盗用构造函数继承实例属性(避免引用值共享)。既可以把方法定义在原型上实现重用,又可以让每个实例都有自己的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; }
SuperType.prototype.sayName = function() { console.log(this.name); };
function SubType(name, age) { SuperType.call(this, name);
this.age = age; }
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() { console.log(this.age); };
let instance1 = new SubType("Nicholas", 29); instance1.colors.push("black");
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
console.log(instance1);
console.log(SubType.prototype);
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
|
组合继承是 JavaScript 中使用最多的继承模式
组合继承保留了 instanceof 操作符和 isPrototypeOf() 方法识别合成对象的能力
但是,组合继承存在效率问题,父类构造函数始终会被调用两次:
在创建子类原型时调用一次
在子类构造函数中调用一次

由于调用了两次 SuperType 构造函数,所以有两组 name 和 colors 属性:一组在实例上,另一组在 SubType 的原型上
原型式继承
原型式继承适用于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] };
let anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob");
let yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie");
console.log(person.friends);
|
寄生式继承
寄生式继承类似寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function object(o) { function F() {} F.prototype = o;
return new F(); }
function createAnother(original) { let clone = object(original);
clone.sayHi = function() { console.log("hi"); };
return clone; }
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] };
let anotherPerson = createAnother(person); anotherPerson.sayHi();
|
寄生式继承适合主要关注对象,不在乎类型和构造函数的场景
通过寄生式继承给对象添加函数会导致函数难以重用
寄生式组合继承
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法(使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型)解决组合继承的效率问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| function object(o) { function F() {} F.prototype = o;
return new F(); }
function inheritPrototype(subType, superType) { let prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; }
SuperType.prototype.sayName = function() { console.log(this.name); };
function SubType(name, age) { SuperType.call(this, name);
this.age = age; }
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () { console.log(this.age); };
let instance = new SubType("Nicholas", 29);
console.log(instance);
console.log(SubType.prototype);
|
使用寄生式组合继承原型链保持不变,instanceof() 和 isPrototypeOf() 正常有效
寄生式组合继承是引用类型继承的最佳模式