原文出處
JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn)
JavaScript高級(jí)程序設(shè)計(jì)(第3版)
1.原型鏈繼承
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;
};
var instance = new SubType();
console.log(instance.getSuperValue()); // true
SubType繼承了SuperType,是通過創(chuàng)建這個(gè)SuperType的實(shí)例,并將該實(shí)例賦值給SubType.property實(shí)現(xiàn)的,實(shí)現(xiàn)的本質(zhì)是重寫原型對(duì)象,代之以一個(gè)新類型的實(shí)例。
該例中的實(shí)例以及構(gòu)造函數(shù)和原型之間的關(guān)系如下圖:

原型鏈繼承的缺點(diǎn):
- 引用類型的屬性被所有實(shí)例共享,舉個(gè)例子:
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
var instance2 = new SubType();
console.log(instance1.colors === instance2.colors); //true
- 在創(chuàng)建 Child 的實(shí)例時(shí),不能向Parent傳參
實(shí)踐中很少會(huì)單獨(dú)使用原型鏈
2.借用構(gòu)造函數(shù)
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//繼承
SuperType.call(this);
}
var instance1 = new SubType();
var instance2 = new SubType();
console.log(instance1.colors === instance2.colors); //false
console.log(instance1 instanceof SubType); //true
console.log(instance1 instanceof SuperType); //false
實(shí)現(xiàn)方式:通過在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)來實(shí)現(xiàn)繼承
優(yōu)點(diǎn):
- 避免了引用類型的屬性被所有實(shí)例共享
- 可以在 Child 中向 Parent 傳參
function SuperType(name) {
this.name = name;
}
function SubType() {
//繼承了 SuperType,同時(shí)還傳遞了參數(shù)
SuperType.call(this, "Nicholas");
//實(shí)例屬性
this.age = 29;
}
var instance = new SubType();
console.log(instance.name); //"Nicholas";
console.log(instance.age); //29
缺點(diǎn):方法都在構(gòu)造函數(shù)中定義,每次創(chuàng)建實(shí)例都會(huì)創(chuàng)建一遍方法。
function SuperType(name) {
this.name = name;
this.sayName = function () {
console.log(this.name);
};
}
function SubType() {
//繼承了 SuperType,同時(shí)還傳遞了參數(shù)
SuperType.call(this, "Nicholas");
//實(shí)例屬性
this.age = 29;
}
var instance1 = new SubType();
var instance2 = new SubType();
console.log(instance1.sayName === instance2.sayName); //false
3.組合繼承
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);
instance1.sayAge();
instance1.sayName();
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
實(shí)現(xiàn)方式: 通過借用構(gòu)造函數(shù)模式來繼承屬性,通過原型鏈模式來繼承方法
優(yōu)點(diǎn):融合原型鏈繼承和構(gòu)造函數(shù)的優(yōu)點(diǎn),是 JavaScript 中最常用的繼承模式。
4.原型式繼承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
var yetAnotherPerson = object(person)
console.log(anotherPerson.friends === yetAnotherPerson.friends);
實(shí)現(xiàn)方式:即創(chuàng)建一個(gè)用于封裝繼承過程的函數(shù),在函數(shù)內(nèi)部將傳入的對(duì)象作為創(chuàng)建的對(duì)象的原型(就是 ES5 Object.create 的模擬實(shí)現(xiàn))。
缺點(diǎn):包含引用類型的屬性值始終都會(huì)共享相應(yīng)的值,這點(diǎn)跟原型鏈繼承一樣。
5.寄生式繼承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original); //通過調(diào)用函數(shù)創(chuàng)建一個(gè)新獨(dú)享
clone.sayHi = function () { //以某種方式來增強(qiáng)這個(gè)對(duì)象
console.log("hi");
};
return clone; //返回這個(gè)對(duì)象
}
var person = {
name: "Nicholas",
friends: ["red", "Court", "van"]
};
var anotherPerson = createAnother(person);
var yetanotherPerson = createAnother(person);
console.log(anotherPerson.sayHi === yetanotherPerson.sayHi); //false
優(yōu)點(diǎn):在原型式繼承的基礎(chǔ)上,增強(qiáng)了對(duì)象
缺點(diǎn):跟借用構(gòu)造函數(shù)模式一樣,每次創(chuàng)建對(duì)象都會(huì)創(chuàng)建一遍方法。
6.寄生組合式繼承
再來看下組合繼承的例子:
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
};
function SubType(name, age) {
SuperType.call(this, name); //第二次調(diào)用 SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次調(diào)用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
alert(this.age);
};
組合繼承最大的缺點(diǎn)是會(huì)調(diào)用兩次父構(gòu)造函數(shù)。在第一次調(diào)用 SuperType 構(gòu)造函數(shù)時(shí),SubType.prototype 會(huì)得到兩個(gè)屬性: name 和 colors ;它們都是 SuperType 的實(shí)例屬性,只不過現(xiàn)在位于 SubType 的原型中。當(dāng)調(diào)用 SubType 構(gòu)造函數(shù)時(shí),又會(huì)調(diào)用一次 SuperType 構(gòu)造函數(shù),這一次又在新對(duì)象上創(chuàng)建了實(shí)例屬性 name 和 colors 。于是,這兩個(gè)屬性就屏蔽了原型中的兩個(gè)同名屬性。
使用寄生組合式繼承可以解決這個(gè)問題,如下:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //創(chuàng)建對(duì)象
prototype.constructor = subType; //增強(qiáng)對(duì)象
subType.prototype = prototype; //指定對(duì)象
}
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); //解決組合繼承缺點(diǎn)
SubType.prototype.sayAge = function() {
console.log(this.age);
};