JavaScript繼承方式詳解

來源:互聯網
上載者:User

標籤:

    JavaScript並不是物件導向的語言,它是基於對象的語言。在JavaScript中一切皆是對象。在 JavaScript中建立自訂對象的方法 一文中,我已經介紹了基本的建立自訂類型的方法。那麼怎麼實作類別型與類型之間的繼承呢?這就是本文要講的內容。

     JavaScript實現繼承的方式主要有兩種: 原型鏈繼承和藉助建構函式繼承

一、原型鏈繼承

     原型鏈繼承的主要思想是將父類型的執行個體賦給子類型的原型,這樣子類型就可以通過原型對象的[[prototype]]訪問到父類型的所有屬性和方法。具體實現方式如下:

function SuperType(){this.property = true;        //SuperType 執行個體屬性 property}SuperType.prototype.getSuperValue = function(){return this.property;        //SuperType 方法 getSuperValue()}function SubType(){this.subProperty = false;    //SubType 執行個體屬性 subProperty};SubType.prototype = new SuperType();    //將SuperType執行個體賦給SubType的原型SubType.prototype.constructor = SubType;SubType.prototype.getSubValue = function(){return this.subProperty;            //SubType 添加方法 getSubValue()};var instance = new SubType();alert(instance.getSuperValue());    // true

 

     父類型SuperType的執行個體中有一個內部指標[[prototype]]指向SuperType的原型,而將這個執行個體賦值給子類型SubType的原型後,instance在搜尋屬性時,會先搜尋自身的屬性,然後搜尋它的原型SubType.prototype中的屬性,包括SuperType的執行個體屬性和後添加自身的原型屬性,然後會繼續搜尋到SuperType.prototype的屬性。這樣SubType就可以訪問到SuperType的全部屬性了。也就是實現了繼承。這個繼承是通過在它們之間建立原型鏈實現的。

     不過原型鏈繼承有一些問題。首先,子類型不能像父類型傳遞參數,其次,由於父類型的執行個體屬性都變成了子類型的原型屬性,那麼那些參考型別的屬性值就會在子類型的所有執行個體中共用,這樣往往跟我們預期的效果不同。

二、藉助建構函式繼承

    相對於原型中參考型別共用的問題,建構函式就有自己的突出優勢,這在之前講解建立對象方法時也提到過。藉助建構函式繼承的思想是在子類建構函式中調用父類建構函式。具體實現方法如下:

function SuperType(name){this.name = name;this.friends = ["Mike", "John"]; }function SubType(){// 調用SuperType建構函式,並為其傳遞一個參數,設定了SubType的name和friends屬性值SuperType.call(this, "Nicholas"); //為SubType添加屬性age  this.age = "22";}var instance1 = new SubType();alert(instance1.name);           // Nicholasinstance1.friends.push("Kate");  alert(instance1.friends);        // Mike,John,Katealert(instance1.age);            // 22var instance2 = new SuperType("Jake");alert(instance2.friends);        // Mike,Johnalert(instance2.name);           // Jake

 

    在SubType的建構函式中通過call()方法調用SuperType的建構函式,這樣所有SubType的執行個體都會有一個firends副本,不存在共用的問題。並且可以在調用SuperType建構函式時傳入參數。

    當然這種方法的缺點也是顯而易見的,它無法實現函數的複用。

三、組合繼承

    組合繼承是JavaScript中運用最普遍的繼承方式,它集合了原型繼承和建構函式繼承的優點,用建構函式繼承父類型的執行個體屬性,用原型鏈繼承父類型的原型方法。具體方法如下:

function SuperType(name){//父類的執行個體屬性this.name = name;this.friends = ["Mike","John"];}//父類的原型方法(函數)SuperType.prototype.sayName = function(){alert(this.name);};function SubType(name,age){//子類的執行個體屬性// 調用父類的建構函式,繼承父類的執行個體屬性name,friends       SuperType.call(this, name);             this.age = age;                 // 子類新添加的執行個體屬性}//將父類的執行個體賦值給子類的原型,原型鏈繼承父類的原型方法sayName() SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType;//子類新添加的方法函數SubType.prototype.sayAge = function()   {alert(this.age);};var instance1 = new SubType("Nicholas", 22);instance1.friends.push("Kate");alert(instance1.friends);     // Mike,John,Kateinstance1.sayAge();           // 22instance1.sayName();          // Nicholasvar instance2 = new SubType("Jake", 25);alert(instance2.friends);     // Mike,Johninstance2.sayAge();           // 25instance2.sayName();          // Jake

 

    在這個例子中,通過在SubType的建構函式中調用SuperType的建構函式,將父類的name和friends屬性添加到子類中。再將一個父類的執行個體 new SuperType() 賦給子類,實現原型鏈的串連,從而繼承了父類的sayName()方法。

    這種方法融合了前兩種方法的優點,看似完美,實際上它還是有一個缺點:它會調用兩次父類的建構函式,從而使子類的建構函式和原型中都包含父類的執行個體屬性。如上例,第一次調用是在講父類的執行個體賦給子類的原型上時,new SuperType(), 這樣,子類的原型中就有了父類的執行個體屬性。第二次調用是在建立子類的執行個體時,new SubType(),由於子類的建構函式中又調用了一次父類的建構函式,所以子類的執行個體中也會包含父類的執行個體屬性。雖然在尋找屬性時,執行個體中的屬性會先被找到而覆蓋掉原型中的屬性,但這樣做肯定是對效能有影響的。那麼怎麼避免這個問題呢?

四、寄生組合式繼承

    我們首先要知道這兩次調用父類的建構函式的真正目的是什麼。第一次調用,將父類的執行個體賦值給子類的執行個體,我們本質上需要的只有指向父類原型的指標[[prototype]]而已,它使父類和子類通過原型鏈串連起來,而父類的執行個體屬性我們並不需要,它的繼承是通過第二次在子類的建構函式中調用實現的。所以,我們現在只要能想出一個方法,能夠獲得指向父類原型的指標,並把它添加到子類的原型上,就可以省掉第一次調用了。

    道格拉斯•克羅克福德在2006年提出了原型式繼承方法,在其中用到了這樣一個函數:

function object(o) {    function F() {}    F.prototype = o;    return new F();}

 

在這個函數中,首先建立了一個臨時的空的建構函式F,然後將參數o賦給F的原型,最後返回一個F的執行個體。如果把這個函數用在繼承的實現中,並將父類的原型對象作為參數傳入,就正好滿足我們之前的要求:F的原型中儲存了父類原型對象的副本,可以將F賦給子類的原型,那麼子類就可以訪問到父類所有的原型屬性了。這個過程可以用下面這個函數封裝起來:

function inheritPrototype(subType, superType) {    var proto = object(superType.prototype);    proto.constructor = subType;    subType.prototype = proto;}

      在函數內部,先建立一個父類的原型副本proto, 然後為其添加constructor屬性,最後將這個副本賦給子類。這樣就可以不通過調用父類的建構函式建立原型鏈。用這個函數代替之前組合繼承方式中的為子類原型賦值的語句就可以了。

function SuperType(name){    this.name = name;    this.friends = ["Mike","John"];}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);};

 

這種寄生組合式繼承是目前效能最好的繼承方式。

JavaScript繼承方式詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.