天天看點

ECMAScript繼承機制實作

繼承機制的實作

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

繼續閱讀