繼承機(jī)制的實(shí)現(xiàn)
ECMAScript中沒有顯示聲明class,類是通過function函數(shù)來創(chuàng)建的。
要用ECMAScript實(shí)現(xiàn)繼承機(jī)制,您可以從要繼承的基類入手。所有開發(fā)者定義的類都可作為基類。出于安全原因,本地類和宿主類不能作為基類,這樣可以防止公用訪問編譯過的瀏覽器級的代碼,因?yàn)檫@些代碼可以被用于惡意攻擊。
<br />
對象冒充(object masquerading)
構(gòu)想原始的ECMAScript時(shí),根本沒打算設(shè)計(jì)對象冒充。它是在開發(fā)者開始理解函數(shù)的工作方式,尤其是如何在函數(shù)環(huán)境中使用this關(guān)鍵字后才發(fā)展出來。
單重繼承
超類(基類)定義如下:
function ClassA(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
子類定義如下:
function ClassB(sColor, sName) {
this.newMethod = ClassA;
this.newMethod(sColor); // ClassA的構(gòu)造函數(shù)被“擴(kuò)展開”,定義了1個(gè)屬性和1個(gè)方法
delete this.newMethod; // 繼承任務(wù)完成,解除對ClassA構(gòu)造函數(shù)的引用
this.name = sName; // 其他屬性定義在后,防止可能被覆蓋
this.sayName = function () {
alert(this.name);
};
}
所有新屬性和新方法都必須在刪除了新方法的代碼行后定義。否則,可能會(huì)覆蓋超類的相關(guān)屬性和方法。
這種方法了實(shí)現(xiàn)的繼承,當(dāng)使用instanceof操作符判斷時(shí),ClassB不屬于ClassA。如下:
var objB = new ClassB("red", "john");
alert(objB instanceof ClassA); // 輸出 "false"
alert(objB instanceof ClassB); // 輸出 "true"
<br />
多重繼承
如果存在兩個(gè)類ClassX和ClassY(并且2個(gè)類的構(gòu)造函數(shù)都使用this指針進(jìn)行定義),ClassZ想繼承這兩個(gè)類,可以使用下面的代碼:
function ClassZ() {
this.newMethod = ClassX;
this.newMethod();
delete this.newMethod;
this.newMethod = ClassY;
this.newMethod();
delete this.newMethod;
}
這里存在一個(gè)弊端,如果存在兩個(gè)類ClassX和ClassY具有同名的屬性或方法,ClassY具有高優(yōu)先級。因?yàn)樗鼜暮竺娴念惱^承。除這點(diǎn)小問題之外,用對象冒充實(shí)現(xiàn)多重繼承機(jī)制輕而易舉。
<br />
call方法
由于這種繼承方法的流行,ECMAScript 的第三版為 Function 對象加入了兩個(gè)方法,即 call() 和 apply()。
call() 方法是與經(jīng)典的對象冒充方法最相似的方法。它的第一個(gè)參數(shù)用作 this 的對象。其他參數(shù)都直接傳遞給函數(shù)自身。
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.call(obj, "The color is ", "a very nice color indeed.");
要與繼承機(jī)制的對象冒充方法一起使用該方法,只需將前三行的賦值、調(diào)用和刪除代碼替換即可:
function ClassB(sColor, sName) {
// this.newMethod = ClassA;
// this.newMethod(color);
// delete this.newMethod;
ClassA.call(this, sColor); // 使用ClassA的call方法實(shí)現(xiàn)繼承
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
<br />
apply()方法
apply() 方法有兩個(gè)參數(shù),用作 this 的對象和要傳遞給函數(shù)的參數(shù)的數(shù)組。例如:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
該方法也用于替換前三行的賦值、調(diào)用和刪除新方法的代碼:
function ClassB(sColor, sName) {
// this.newMethod = ClassA;
// this.newMethod(color);
// delete this.newMethod;
ClassA.apply(this, new Array(sColor)); // 使用ClassA的apply方法實(shí)現(xiàn)繼承
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
<br />
原型鏈(prototype chaining)
prototype對象是個(gè)模板,要實(shí)例化的對象都以這個(gè)模板為基礎(chǔ)??偠灾?,prototype對象的任何屬性和方法都被傳遞給那個(gè)類的所有實(shí)例。原型鏈利用這種功能來實(shí)現(xiàn)繼承機(jī)制。
原型鏈會(huì)用另一類型的對象重寫類的 prototype 屬性。
如果用原型方式重定義前面例子中的類,它們將變?yōu)橄铝行问剑?/p>
function ClassA() {
}
ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB() {
}
ClassB.prototype = new ClassA(); // 利用原型鏈實(shí)現(xiàn)繼承
ClassB.prototype.name = ""; // 其他屬性定義在后,防止被覆蓋
ClassB.prototype.sayName = function () {
alert(this.name);
};
與對象冒充相似,子類的所有屬性和方法都必須出現(xiàn)在 prototype 屬性被賦值后,因?yàn)樵谒百x值的所有方法都會(huì)被刪除。
注意:調(diào)用ClassA的構(gòu)造函數(shù),沒有給它傳遞參數(shù)。這在原型鏈中是標(biāo)準(zhǔn)做法。要確保構(gòu)造函數(shù)沒有任何參數(shù)。
此外,在原型鏈中,instanceof運(yùn)算符的運(yùn)行方式也很獨(dú)特。對ClassB的所有實(shí)例,instanceof為ClassA和ClassB都返回true。例如:
var objB = new ClassB();
alert(objB instanceof ClassA); // 輸出 "true"
alert(objB instanceof ClassB); // 輸出 "true"
在 ECMAScript 的弱類型世界中,這是極其有用的工具,不過使用對象冒充時(shí)不能使用它。
原型鏈的弊端是不支持多重繼承。
<br />
混合方式
對象冒充的主要問題是必須使用構(gòu)造函數(shù)方式,這不是最好的選擇。不過如果使用原型鏈,就無法使用帶參數(shù)的構(gòu)造函數(shù)了。開發(fā)者如何選擇呢?答案很簡單,兩者都用。
我們曾經(jīng)講解過創(chuàng)建類的最好方式是用構(gòu)造函數(shù)定義屬性,用原型定義方法。這種方式同樣適用于繼承機(jī)制,用對象冒充繼承構(gòu)造函數(shù)的屬性,用原型鏈繼承 prototype 對象的方法。用這兩種方式重寫前面的例子,代碼如下:
function ClassA(sColor) {
this.color = sColor;
}
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB(sColor, sName) {
ClassA.call(this, sColor); // 使用[對象冒充]繼承屬性
this.name = sName;
}
ClassB.prototype = new ClassA(); // 使用[原型定義]繼承方法
ClassB.prototype.sayName = function () {
alert(this.name);
};
混合方式實(shí)現(xiàn)的繼承,使用instanceof運(yùn)算符判斷時(shí),ClassB屬于ClassA,如下:
var objB = new ClassB("red", "john");
alert(objB instanceof ClassA); // 輸出 "true"
alert(objB instanceof ClassB); // 輸出 "true"
<br />
更多請參考:W3School