直接利用原型链继承
这是最简单粗暴的继承方式。让构造函数 A 的原型对象是构造函数 B 的实例,即 A.prototype = new B()。这样一来,A 的实例的原型就会指向 B 的实例,A 通过原型链就能访问到 B 的属性和方法。假设 B 的原型又是 C 的实例,即 B 继承了 C,沿着原型链一直向上,就构成了实例与原型的链条,也实现了继承。
function SuperType () {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType () {
this.subproperty = false;
}
/* 继承 SuperType */
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.getSubValue = function () {
return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue()); /* true */
上面的例子中,getSuperValue 定义在 SuperType 的原型中,而 property 定义在了 SuperType 的实例中。随着继承关系的实现, SuperType 的实例成为了 SubType 的原型,property 也就定义在了 SubType 的原型中了。随后,我们将 SubType 原型的 constructor 修改为 Subtype,并在其中定义了 getSubValue 方法。SubType 的构造函数还在其实例中定义了 subproperty 属性。
当我们通过 instance 实例访问 getSuperValue 函数时,查找顺序为:instance -> Subtype.prototype(也是SuperType的实例) -> SubType.prototype.__proto__(即SuperType.prototype),最终在 SuperType 的原型中找到了这个函数并调用。
这种方式存在与「利用原型模式创建对象」类似的问题:构造函数 SuperType 的实例成为 SubType 的原型,那么 SubType 的原型中存在 SuperType 的实例属性,这些属性将会被 SubType 的实例共享。
另一个问题是,无法向 SuperType 传递初始化参数。
借用构造函数
也叫伪造对象或经典继承,即在子类的构造函数中调用父类的构造函数。究其根源,也就是将父类构造函数定义属性和函数的操作由子类构造函数完成,原本父类构造函数会在父类实例中定义这些属性和函数,现在子类也在自己的实例中定义了这些属性和函数。别忘了,作用域链上,子类会屏蔽对父类的同名属性和函数的访问。
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
function SubType (name) {
/* 继承了 SuperType */
SuperType.call(this, name);
this.age = 27;
}
var instance1 = new SubType('Nocholas');
instance1.colors.push('black'); /* colors 是借用 SuperType() 定义在 instance1 上的实例属性 */
console.log(instance1.colors); /* 'red, blue, green, black' */
var instance2 = new SubType('Grey');
console.log(instance2.colors); /* 'red, blue, green' */
console.log(instance2.name); /* 'Grey' */
console.log(instance2.age); /* 27 */
如果仅仅是借用构造函数,那么也就无法避免构造函数模式存在的问题 —— 函数都在构造函数中定义,无法复用。
组合继承
组合继承 = 原型链 + 借用构造函数
- 原型链:继承父类的原型的属性和函数
- 借用构造函数:将父类构造函数定义的属性在子类实例中定义
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Super.prototype.sayName = function () {
console.log(this.name);
}
function SubType (name, age) {
/* 继承 SuperType 属性 */
SuperType.call(this, name);
this.age = age;
}
/* 继承 SuperType 方法 */
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
}
我们利用 SuperType.call(this, name) 将 SuperType 的属性定义到了 SubType 的实例中,也就不存在共享属性的问题了;又利用 SubType.prototype = new SuperType() 为 SubType 提供了 SuperType 原型中的函数。这样一来,SuperType 的属性和函数都得到了继承,且属性不被共享、函数是共享的。
有一个小细节值得留意,由于 SubType.prototype 是 SuperType 的实例,所以也会存在 name 和 colors 属性,这是我们使用原型链模式带来的附属产品。只不过由于 SubType 实例中存在同名属性,「屏蔽」了对 SubType.prototype.name 和 SubType.prototype.age 的访问而已。后面我们还会再优化它。
原型式继承
准确的讲,我认为「原型式继承」只是新建了一个原型指向传入对象的空白对象。
function object (o) {
function F () {};
F.prototype = o;
return new F();
}
这种继承方法虽然不会作为直接可以使用的继承方式,但可以作为其他继承方式的一种工具。千万不要忘记,o 作为原型,其属性是被共享的。
ES5 中规范化了原型式继承 —— Object.create() 方法。它接收两个参数,第一个参数与 object(o) 的参数相同;第二个参数可选,可为返回对象定义新的属性。
寄生式继承
寄生式继承的思路与寄生构造函数和工厂函数类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像是真地是它做了所有工作一样返回对象。
function createAnother (original) {
var clone = object(original); /* 通过调用函数创建一个新对象 */
clone.sayHi = function () { /* 以某种方式来增强这个对象 */
console.log('Hi');
}
return clone; /* 返回这个对象 */
}
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); /* 'Hi' */
anotherPerson 以 person 为原型,具有 person 的所有属性和函数,而且在实例上定义了自己的 sayHi 函数。
仅适用于不考虑自定义类型和构造函数的情况下可以使用这种模式。但由于不能做到函数复用,所以还不是最完美的方式。
寄生组合式继承
前面提到的组合继承已经是相对比较好的继承方式了。但它调用了两次 SuperType 构造函数,使得 SubType 原型和实例中拥有 name 和 colors 属性的两份拷贝。
我们改进的思路是:
不必为了指定 SubType 的原型而调用 SuperType 的构造函数,我们需要的无非就是 SuperType 原型的一个副本而已。
我们使用寄生式继承来继承 SuperType 的原型。
function inheritPrototype (subType, superType) {
var 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);
}
使用寄生组合式继承,我们既实现了原型链,又避免了组合继承的两个调用构造函数的问题。