js 組合繼承詳解

在下月亮有何貴幹發表於2021-11-04


前言


首先學習繼承之前,要對原型鏈有一定程度的瞭解。

不瞭解可以去先閱讀我另一篇文章,裡面對原型鏈有一個較為詳細的說明:js 原型鏈詳解

如果已經瞭解請繼續。

之前寫過一篇博文將繼承方式全部列出來了,不過我發現一口氣看完過於長了,也不利於吸收知識,所以我先將組合繼承部分劃分出來,後續會把寄生部分補上。

原型鏈繼承


父類例項作為子類的原型
子類創造的兩個例項的隱式原型__proto__指向父類的那個例項
而父類的例項的隱式原型__proto__又指向父類的原型father.prototype
根據原型鏈的特性,所有子類的例項能夠繼承父類原型上的屬性。

閱覽以下這張圖可以配合程式碼理解清晰。
在這裡插入圖片描述

    //父類
    function father() {
      this.fatherAttr = ["fatherAttr"];
    }
    
    //父類的原型上的屬性
    father.prototype.checkProto = "checkProto";

    //子類
    function child() {}

    // 將father例項作為child這個建構函式的原型
    child.prototype = new father();
    child.prototype.constructor = child;

    //兩個子類例項
    const test1 = new child();
    const test2 = new child();

    console.log("測試1:");
    console.log("test1:", test1);
    console.log("test2:", test2);
    console.log("test1.fatherAttr:", test1.fatherAttr);
    console.log("test2.fatherAttr:", test2.fatherAttr);

    console.log("測試2:");
    test1.fatherAttr.push("newAttr");
    console.log("test1.fatherAttr:", test1.fatherAttr);
    console.log("test2.fatherAttr:", test2.fatherAttr);

    console.log("測試3:");
    console.log("test1.checkProto:", test1.checkProto);

在這裡插入圖片描述

特點:

  1. 兩個例項物件都沒有fatherAttr屬性,但是因為父類的例項會擁有fatherAttr屬性,且現在父類的例項作為child的原型,根據原型鏈,他們可以共享到自己的建構函式child的原型上的屬性。(測試1)
  2. 因為只有一個父類的例項作為他們的原型,所以所有例項共享了一個原型上的屬性fatherAttr,當原型上的屬性作為引用型別時,此處是陣列,test1新增一個新內容會導致test2上的fatherAttr也改變了。(測試2)(缺點
  3. child建構函式不能傳遞入參。(缺點
  4. 例項可以訪問到父類的原型上的屬性,因此可以把可複用方法定義在父類原型上。(測試3)

建構函式繼承


將父類上的this繫結到子類,也就是當子類創造例項時會在子類內部呼叫父類的建構函式,父類上的屬性會拷貝到子類例項上,所以例項會繼承這些屬性。

    //父類
    function father(params) {
      this.fatherAttr = ["fatherAttr"];
      this.params = params;
    }

    //父類的原型上的屬性
    father.prototype.checkProto = "checkProto";

    //子類
    function child(params) {
      father.call(this, params);
    }

    //兩個子類例項
    const test1 = new child("params1");
    const test2 = new child("params2");

    console.log("測試1:");
    console.log("test1:", test1);
    console.log("test2:", test2);
    console.log("test1.fatherAttr:", test1.fatherAttr);
    console.log("test2.fatherAttr:", test2.fatherAttr);

    console.log("測試2:");
    test1.fatherAttr.push("newAttr");
    console.log("test1.fatherAttr:", test1.fatherAttr);
    console.log("test2.fatherAttr:", test2.fatherAttr);
    
    console.log("測試3:");
    console.log("test1.checkProto:", test1.checkProto);

在這裡插入圖片描述

特點:

  1. 兩個例項物件都擁有了拷貝來的fatherAttr屬性,所以沒有共享屬性,創造一個例項就得拷貝一次父類的所有屬性,且因為不能繼承父類原型,所以方法不能複用,被迫拷貝方法。(測試1)(缺點
  2. test1新增一個新內容只是改變了test1自己的屬性,不會影響到test2。(測試2)
  3. child建構函式可以傳遞引數,定製自己的屬性。(測試1)
  4. 例項不能繼承父類的原型上的屬性。(測試3)(缺點

組合繼承


結合原型鏈繼承和建構函式繼承,可以根據兩種繼承特點進行使用。

    //父類
    function father(params) {
      this.fatherAttr = ["fatherAttr"];
      this.params = params;
    }

    //父類的原型上的屬性
    father.prototype.checkProto = "checkProto";

    //子類
    function child(params) {
      //第二次呼叫了父類建構函式
      father.call(this, params);
    }

    // 將father例項作為child建構函式的原型
    child.prototype = new father();//第一次呼叫了父類建構函式
    child.prototype.constructor = child;

    //兩個例項
    const test1 = new child("params1");//從這裡跳轉去子類建構函式第二次呼叫了父類建構函式
    const test2 = new child("params2");

    console.log("測試1:");
    console.log("test1:", test1);
    console.log("test2:", test2);
    console.log("test1.fatherAttr:", test1.fatherAttr);
    console.log("test2.fatherAttr:", test2.fatherAttr);

    console.log("測試2:");
    test1.fatherAttr.push("newAttr");
    console.log("test1.fatherAttr:", test1.fatherAttr);
    console.log("test2.fatherAttr:", test2.fatherAttr);

    console.log("測試3:");
    console.log("test1.checkProto:", test1.checkProto);

    console.log("測試4:");
    delete test1.fatherAttr
    console.log("test1:", test1);
    console.log("test1.fatherAttr:", test1.fatherAttr);

在這裡插入圖片描述

特點:

  1. 兩個例項物件都擁有了拷貝來的fatherAttr屬性,創造一個例項就得拷貝一次父類的所有屬性(建構函式繼承特點,測試1),但是能訪問父類原型,可以把複用方法定義在父類原型上。(原型鏈繼承特點,測試1)
  2. test1新增一個新內容只是改變了test1自己的屬性,不會影響到test2。(建構函式繼承特點,測試2)
  3. child建構函式可以傳遞引數,定製自己的屬性。(建構函式繼承特點,測試1)
  4. 例項能繼承父類的原型上的屬性。(原型鏈繼承特點,測試3)
  5. 呼叫了兩次父類的建構函式,生成兩份例項,建立子類原型鏈一次,用子類建立例項時,子類內部裡面一次,第二次覆蓋了第一次。(缺點
  6. 因為呼叫兩次父類建構函式,如果用delete刪除例項上拷貝來的fatherAttr屬性,例項仍然擁有隱式原型指向的父類例項上的fatherAttr屬性。(原型鏈繼承特點,測試4)(缺點

相關文章