最詳細的JavaScript高級教程(十八)高級原型鏈

經典繼承(借用構造函數)

爲了解決之前提到的超類型構造函數中引用類型的問題,我們可以使用借用構造函數的方式

  function SuperType() {
    this.property = ['red'];
  }
  function SubType() {
    SuperType.call(this); // 借用構造函數
  }
  var instance = new SubType();
  alert(instance instanceof SubType);
  alert(instance instanceof SuperType);

我們分析一下這種方式:

在SubType中通過call,讓SuperType的構造函數在自己的運行環境中運行,SuperType中的this就指向了SubType的作用域,等於給SubType添加了property的對象,故實現了每個實例有自己獨立的超類型引用對象。

但是這種方法缺點太過於明顯:

  1. 這樣寫,這兩個對象沒有繼承關係了,像代碼中使用的instanceof運算符,不能確定實例和超類的關係了。
  2. 之前我們在創建對象的時候講過,將對象屬性的定義寫道構造函數中,將方法的定義寫到原型中可以避免方法的重複創建,這種模式無法識別不寫在構造函數中的代碼。

組合繼承(推薦)

其實上面的方式我們已經很接近成功了,爲了解決缺點1,我們把SubType的原型指向SuperType的一個實例看看會發生什麼情況。

  function SuperType() {
    this.property = ['red'];
  }
  function SubType() {
    SuperType.call(this);
  }
  SubType.prototype = new SuperType();
  var instance = new SubType();
  alert(instance instanceof SubType);
  alert(instance instanceof SuperType); //true
  var instance2 = new SubType();
  instance.property.push('blue');
  alert(instance2.property); //red

我們發現,當我們這麼做的時候,既維持了繼承關係,也爲每一個實例創建了自己的property屬性。

這種方式是我們在實際應用中最常使用的方式。

原型式繼承

當我們不需要創建一類型實例,而是隻是想簡單的創建一個對象的副本的時候,我們既可以用原型式繼承,其作用就像是創建了對象的一個克隆,但是這個克隆是淺複製的。

其原理很簡單,就是下面的方法

// 本質是返回一個以傳入對象爲原型對象的實例
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

這種方法可以輕量級的創建一個之前對象的副本,注意其是淺複製的,無法避免引用類型的問題。

ES5對於這種創建方式進行了規範化,使用下面的方法 Object.create:

  var person = {
    name: 'nic',
    friend: ['red']
  };
  var another = Object.create(person);
  var another2 = Object.create(person, {
    name: {
      // 注意這裏的屬性描述必須是一個對象
      value: 'Greg'
    }
  });
  alert(another2.name);

注意第二個參數是需要複寫的屬性的屬性描述符,不能直接寫 屬性:值。

寄生組合繼承(標準繼承)

當我們使用組合繼承的時候,需要把子類型的prototype屬性賦值爲父類型的一個實例,這就導致父類型的構造函數被調用了兩次,一次是在SuperType.call(this);的時候,一次是在SubType.prototype = new SuperType();的時候。構造函數多次的執行會導致性能的消耗,所以爲了避免這種情況,結合我們之前講的對象複製的方法,我們使用下面這種寄生組合模式。

  function inheritPrototype(subType, superType) {
    // 使用複製的方法,創建出的對象不會打破原型鏈
    // 如果不理解這裏,返回去看object.create的實現
    var prototype = Object.create(superType.prototype); 
    prototype.constructor = subType;
    subType.prototype = prototype;
  }
  function SuperType() {
    this.property = ['red'];
  }
  function SubType() {
    SuperType.call(this);  //引用類型都複製出了副本
  }
  inheritPrototype(SubType, SuperType);
  var instance = new SubType();
  alert(instance instanceof Object); //true
  alert(instance instanceof SuperType); //true
  alert(instance instanceof SubType); //true

我們注意下面幾點:

  1. 爲什麼說用Object.create節省了性能呢?因爲Object.create的實現是:
    function object(o) {
        // 這裏等於返回了一個o的構造函數中沒有代碼的子類
        // 在寄生組合模式中,返回的這個子類正好用於做SubType的prototype
        function F() {} 
        F.prototype = o;
        return new F();
    }
    
    其中,F構造函數中沒有代碼,節省性能,而調用SuperType的構造方法,裏面代碼很多,初始化了很多屬性,但是在SuperType.call(this);的時候,這些屬性又被重新初始化出了一個副本,所以這部分性能就等於浪費掉了
  2. 由於前面講的Object.create的實現,所以不影響原型鏈
  3. 注意inheritPrototype中constructor的引用指向要處理對
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章