ECMAScript繼承機(jī)制實(shí)現(xiàn)

繼承機(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容