組合繼承(combination inheritance),有時候也叫作僞經典繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性。下面來看一個例子:函數
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); //繼承屬性 this.age = age; } SubType.prototype = new SuperType(); //繼承方法 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27
在這個例子中,SuperType 構造函數定義了兩個屬性:name 和 colors。SuperType 的原型定義了一個方法 sayName()。SubType 構造函數在調用 SuperType 構造函數時傳入了 name 參數,緊接着又定義了它本身的屬性 age。而後,將 SuperType 的實例賦值給 SubType 的原型,而後又在該新原型上定義了方法 sayAge()。這樣一來,就可讓兩個不一樣的 SubType 實例既分別擁有本身屬性——包括 colors 屬性,又可使用相同的方法了。組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,成爲 JavaScript 中最經常使用的繼承模式。並且,instanceof和 isPrototypeOf()也可以用於識別基於組合繼承建立的對象。this
前面說過,組合繼承是 JavaScript 最經常使用的繼承模式;不過,它也有本身的不足。組合繼承最大的問題就是不管什麼狀況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。沒錯,子類型最終會包含超類型對象的所有實例屬性,但咱們不得不在調用子類型構造函數時重寫這些屬性。再來看一看下面組合繼承的例子。prototype
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); //第二次調用 SuperType() this.age = age; } SubType.prototype = new SuperType(); //第一次調用 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { alert(this.age); };
在第一次調用 SuperType 構造函數時,SubType.prototype 會獲得兩個屬性:name 和 colors;它們都是 SuperType 的實例屬性,只不過如今位於 SubType 的原型中。當調用 SubType 構造函數時,又會調用一次 SuperType 構造函數,這一次又在新對象上建立了實例屬性 name 和 colors。因而,這兩個屬性就屏蔽了原型中的兩個同名屬性。code
好在咱們已經找到了解決這個問題方法——寄生組合式繼承。所謂寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。其背後的基本思路是:沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。寄生組合式繼承的基本模式以下所示。對象
function inheritPrototype (subType, superType) { var prototype = object(superType.prototype); //建立對象 prototype.constructor = subType; //加強對象 subType.prototype = prototype; //指定對象 }
這個示例中的 inheritPrototype()函數實現了寄生組合式繼承的最簡單形式。這個函數接收兩個參數:子類型構造函數和超類型構造函數。在函數內部,第一步是建立超類型原型的一個副本。第二步是爲建立的副本添加 constructor 屬性,從而彌補因重寫原型而失去的默認的 constructor 屬性。最後一步,將新建立的對象(即副本)賦值給子類型的原型。這樣,咱們就能夠用調用 inherit-Prototype()函數的語句,去替換前面例子中爲子類型原型賦值的語句了,例如:繼承
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); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function () { alert(this.age); };
這個例子的高效率體如今它只調用了一次 SuperType 構造函數,而且所以避免了在 SubType.prototype 上面建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變;所以,還可以正常使用instanceof 和 isPrototypeOf()。開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式。ip