JavaScript 的動態原型組合繼承

動態原型組合繼承

JavaScript ES5 的類繼承

Zakas久負盛名的《JavaScript高級程序設計(第三版)》完成于2012年,當時ES5剛剛成為標準,ES6還沒有浮出水面,class還只是是保留字,由于ES5對類的支持不夠完善,特別是類的構造和繼承,Zakas用了整整一章來解釋,第六章第三部分在討論類繼承時,提到了6種繼承方式:

  1. 原型鏈
  2. 借用構造函數
  3. 組合繼承
  4. 原型式繼承
  5. 寄生式繼承
  6. 寄生組合式繼承

作者最為推崇的是第三種組合繼承方式,但是在后續的章節中,也提到了這種方式的缺點,那就是父類的構造函數實際上被調用了兩次,在后續的寄生組合繼承模式中有一些改進,用如下函數消除多余的一次原型副本創建(見原書P172):

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

這個函數創建一個父類原型副本,將父類的方法傳遞給了子類,但在這個模式中,雖然省略了創建父類型的副本,但還是創建了父類原型的一個副本,能不能連父類型原型副本的構造步驟也省略呢?

在其他面向對象語言中,子類重用父類的方法一般是通過函數指針的方法實現的,JavaScript的函數沒有簽名,子類不能直接獲得函數方法,但是可以顯示地將父類方法賦予給子類,在這個思路下,我嘗試修改inheritPrototype函數,將父類原型的函數直接賦予給子類原型,既達到了重用的目的,又避免了多余的副本創建,代碼示例如下:

function inheritPrototype(sub, base) {
  for (var propertyName in base.prototype) {
    if (typeof base.prototype[propertyName] === 'function') {
      sub.prototype[propertyName] = base.prototype[propertyName];
    }
  }
}

這個函數遍歷父類原型中所有的方法,然后將方法直接賦予給子類原型,這樣子類就可以完全重用父類的功能代碼,也避免了創建對象。

改造之后,不僅達到了預期,還可以使用構造函數來創建類的實例,類的行為和其他的OO語言也沒有區別,完整代碼如下所示,在nodejs環境中運行一切Ok。

'use strict';
//===================
main();
//===================
function main() {

    var tmp1 = new Person('Dani', 28);
    tmp1.sayName();
    tmp1.sayAge();

    var tmp2 = new Worker('Kayden', 34, 'Actress');
    tmp2.sayName();
    tmp2.sayAge();
    tmp2.sayJob();

    var tmp3 = new Worker('Luna', 30, 'Writer');
    tmp3.friends.push('Sunny');
    tmp3.sayName();
    tmp3.sayAge();
    tmp3.sayJob();

    return 0;
}

function printf(value) {
    console.log(value);
}

// sub類繼承base類原型的所有方法
function inheritPrototype(sub, base) {
  for (var propertyName in base.prototype) {
    if (typeof base.prototype[propertyName] === 'function') {
      printf('Subtype inherited a function: ' + propertyName);
      sub.prototype[propertyName] = base.prototype[propertyName];
    }
  }
}

//父類
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.friends = ['Jenna', 'Carrera', 'Tori'];
    if (typeof this.sayName !== 'function') {
        Person.prototype.sayName = function() {
            printf(this.name);
        };
        Person.prototype.sayAge = function() {
            printf(this.age);
        };
    }
}

// 子類
function Worker(name, age, job) {
    Person.call(this, name, age);
    this.job = job;
    if (typeof this.sayName !== 'function') {
        // 組合繼承方式,為了重用父類方法,構造了一個多余的父類
        // Worker.prototype = new Person(); 
        
        // 寄生組合方式,改造就在此函數之內
        inheritPrototype(Worker, Person);

        Worker.prototype.sayJob = function() {
            printf(this.job);
        };
    }
}

寫在后面的話

ES6已經完全支持class,構造和繼承變得非常簡單,和其他OO語言已經大同小異了,不過考慮到目前還有很多瀏覽器對ES6的支持不夠完善,ES5的類繼承還會陪伴我們走過很長一段時間。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文先對es6發布之前javascript各種繼承實現方式進行深入的分析比較,然后再介紹es6中對類繼承的支持以及...
    lazydu閱讀 16,727評論 7 44
  • 我們在對象創建模式中討論過,對象創建的模式就是定義對象模板的方式。有了模板以后,我們就可以輕松地創建多個結構相同的...
    csRyan閱讀 907評論 0 7
  • 0 寫在前面的話 大多數的面向對象編程語言中,比如C++和Java,在使用他們完成任務之前,必須創建類(class...
    自度君閱讀 1,020評論 0 3
  • 前言 寫這篇筆記的初衷,是想進一步了解ES6的特性extends是如何實現了繼承,查看源碼后發現核心的一段不能理解...
    Jmingzi_閱讀 1,201評論 2 4
  • class的基本用法 概述 JavaScript語言的傳統方法是通過構造函數,定義并生成新對象。下面是一個例子: ...
    呼呼哥閱讀 4,127評論 3 11