继承分为:接口继承和实现继承。接口继承只继承方法签名,实现继承则继承实际方法。 ES只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例之间的关系:
- 每个构造函数都有一个原型对象
- 每个原型对象都包含一个指向构造函数的指针
- 每个实例都包含一个指向原型对象的内部指针
原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系仍然成立,如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。
原型链的基本模式:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
// 继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
// 测试
var instance = new SubType();
console.log(instance.getSuperValue()); // true;

调用instance.getSuperValue()会经历三个搜索步骤:
- 搜索实例;
- 搜索 SubType.prototype;
- 搜索 SuperType.prototype,最后一步才会找到该方法。
在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
原型链的问题:
- 最主要的问题来自包含引用类型值的原型(参考)
- 在创建子类性的实例时,不能向超类型的构造函数中传递参数。实际上应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
实践中很少会单独使用原型链
借用构造函数(伪造对象或经典继承)
基本思想:在子类型构造函数的内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象,因此通过使用apply()或者call()方法也可以在(将来)新创建的对象上执行构造函数。
function SuperType() {
this.colors = ['red', 'green','blue'];
}
function SubType() {
// 继承了SuperType
SuperType.call(this);
}
var instance1 = new SubType();
var instance2 = new SubType();
instance1.colors.push('black'); // ['red', 'green', 'blue', 'black']
instance2.colors; // ['red', 'green', 'blue']
借用构造函数的优势在于可以在子类型构造函数中向超类型构造函数传递参数
function SuperType(name) {
this.name = name;
}
function SubType() {
// 继承了SuperType
SuperType.call(this, "Jack");
this.age = 29;
}
var instance = new SubType();
console.log(instance.name); // Jack
console.log(instance.age); // 29
借用构造函数的问题:
- 无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用无从谈起
- 超类型的原型中定义的方法,对子类型而言不可见,结果所有类型都只能使用构造函数模式
借用构造函数的技术很少单独使用
组合继承(伪经典继承)
将原型链和借用构造函数的技术组合在一起,发挥二者之长的一种继承模式。背后的思路是使用原型链实现对原型属性和方法的继承。这样既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。
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.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'green','blue', 'black']
instance1.sayName(); // 'Nicholas'
instance1.sayAge(); // 29
var instance2 = new SubType('Greg', 27);
console.log(instance2.colors); // ['red', 'green','blue']
instance2.sayName(); // 'Greg'
instance2.sayAge(); // 27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,称为JS中最常用的继承模式。而且,instanceof和isPrototypeOf也能够识别基于组合继承创建的对象。
原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制
var person = {
name: 'Jack',
friends: ['A', 'B', 'C']
}
var anotherPerson = object(person);
anotherPerson.name = 'Alice';
anotherPerson.friends.push('D');
var otherPerson = object(person);
otherPerson.name = 'Bob';
otherPerson.friends.push('E');
console.log(person.friends); // ['A', 'B', 'C', 'D', 'E']
这种原型式继承,要求必须要有一个对象可以作为另一个对象的基础。
ES5通过新增Object.create()方法规范化了这种原型式继承,接收两个参数:
- 一个用作新对象原型的对象
- 一个用作为新对象定义额外属性的对象(可选)
var person = {
name: 'Jack',
friends: ['A', 'B', 'C']
}
var anotherPerson = Object.create(person, {
name: {
value: 'Alice'
}
});
anotherPerson.friends.push('D');
var otherPerson = Object.create(person);
otherPerson.name = 'Bob';
otherPerson.friends.push('E');
console.log(person.friends); // ['A', 'B', 'C', 'D', 'E']
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过包含引用类型的值的属性始终都会共享相同的值,就像使用原型模式一样。
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
function createAnother(original) {
var clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强对象
console.log('hi');
}
return clone; // 返回这个对象
}
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承是一种有用的模式。object()函数表示必需的,任何能够返回新对象的函数都适用于此模式。(因为不能做到函数复用而降低效率)
寄生组合式继承
组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:
- 一次是在创建子类型原型的时候
- 另一次是在子类型构造函数内部
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); // 第二次调用SuperType()
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); // 第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们需要只是超类型的一个副本。本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类性的原型。寄生组合式继承的基本模式:
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
寄生组合式继承是引用类型最理想的继承范式。