繼承機制的實作
ECMAScript中沒有顯示聲明class,類是通過function函數來建立的。
要用ECMAScript實作繼承機制,您可以從要繼承的基類入手。所有開發者定義的類都可作為基類。出于安全原因,本地類和宿主類不能作為基類,這樣可以防止公用通路編譯過的浏覽器級的代碼,因為這些代碼可以被用于惡意攻擊。
對象冒充(object masquerading)
構想原始的ECMAScript時,根本沒打算設計對象冒充。它是在開發者開始了解函數的工作方式,尤其是如何在函數環境中使用this關鍵字後才發展出來。
單重繼承
超類(基類)定義如下:
function ClassA(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
子類定義如下:
function ClassB(sColor, sName) {
this.newMethod = ClassA;
this.newMethod(sColor); // ClassA的構造函數被“擴充開”,定義了1個屬性和1個方法
delete this.newMethod; // 繼承任務完成,解除對ClassA構造函數的引用
this.name = sName; // 其他屬性定義在後,防止可能被覆寫
this.sayName = function () {
alert(this.name);
};
}
所有新屬性和新方法都必須在删除了新方法的代碼行後定義。否則,可能會覆寫超類的相關屬性和方法。
這種方法了實作的繼承,當使用instanceof操作符判斷時,ClassB不屬于ClassA。如下:
var objB = new ClassB("red", "john");
alert(objB instanceof ClassA); // 輸出 "false"
alert(objB instanceof ClassB); // 輸出 "true"
多重繼承
如果存在兩個類ClassX和ClassY(并且2個類的構造函數都使用this指針進行定義),ClassZ想繼承這兩個類,可以使用下面的代碼:
function ClassZ() {
this.newMethod = ClassX;
this.newMethod();
delete this.newMethod;
this.newMethod = ClassY;
this.newMethod();
delete this.newMethod;
}
這裡存在一個弊端,如果存在兩個類ClassX和ClassY具有同名的屬性或方法,ClassY具有高優先級。因為它從後面的類繼承。除這點小問題之外,用對象冒充實作多重繼承機制輕而易舉。
call方法
由于這種繼承方法的流行,ECMAScript 的第三版為 Function 對象加入了兩個方法,即 call() 和 apply()。
call() 方法是與經典的對象冒充方法最相似的方法。它的第一個參數用作 this 的對象。其他參數都直接傳遞給函數自身。
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.");
要與繼承機制的對象冒充方法一起使用該方法,隻需将前三行的指派、調用和删除代碼替換即可:
function ClassB(sColor, sName) {
// this.newMethod = ClassA;
// this.newMethod(color);
// delete this.newMethod;
ClassA.call(this, sColor); // 使用ClassA的call方法實作繼承
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
apply()方法
apply() 方法有兩個參數,用作 this 的對象和要傳遞給函數的參數的數組。例如:
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."));
該方法也用于替換前三行的指派、調用和删除新方法的代碼:
function ClassB(sColor, sName) {
// this.newMethod = ClassA;
// this.newMethod(color);
// delete this.newMethod;
ClassA.apply(this, new Array(sColor)); // 使用ClassA的apply方法實作繼承
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
原型鍊(prototype chaining)
prototype對象是個模闆,要執行個體化的對象都以這個模闆為基礎。總而言之,prototype對象的任何屬性和方法都被傳遞給那個類的所有執行個體。原型鍊利用這種功能來實作繼承機制。
原型鍊會用另一類型的對象重寫類的 prototype 屬性。
如果用原型方式重定義前面例子中的類,它們将變為下列形式:
function ClassA() {
}
ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB() {
}
ClassB.prototype = new ClassA(); // 利用原型鍊實作繼承
ClassB.prototype.name = ""; // 其他屬性定義在後,防止被覆寫
ClassB.prototype.sayName = function () {
alert(this.name);
};
與對象冒充相似,子類的所有屬性和方法都必須出現在 prototype 屬性被指派後,因為在它之前指派的所有方法都會被删除。
注意:調用ClassA的構造函數,沒有給它傳遞參數。這在原型鍊中是标準做法。要確定構造函數沒有任何參數。
此外,在原型鍊中,instanceof運算符的運作方式也很獨特。對ClassB的所有執行個體,instanceof為ClassA和ClassB都傳回true。例如:
var objB = new ClassB();
alert(objB instanceof ClassA); // 輸出 "true"
alert(objB instanceof ClassB); // 輸出 "true"
在 ECMAScript 的弱類型世界中,這是極其有用的工具,不過使用對象冒充時不能使用它。
原型鍊的弊端是不支援多重繼承。
混合方式
對象冒充的主要問題是必須使用構造函數方式,這不是最好的選擇。不過如果使用原型鍊,就無法使用帶參數的構造函數了。開發者如何選擇呢?答案很簡單,兩者都用。
我們曾經講解過建立類的最好方式是用構造函數定義屬性,用原型定義方法。這種方式同樣适用于繼承機制,用對象冒充繼承構造函數的屬性,用原型鍊繼承 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);
};
混合方式實作的繼承,使用instanceof運算符判斷時,ClassB屬于ClassA,如下:
var objB = new ClassB("red", "john");
alert(objB instanceof ClassA); // 輸出 "true"
alert(objB instanceof ClassB); // 輸出 "true"
更多請參考:W3School