JavaScript組合繼承詳解
1、前言
首先學習繼承之前,要對原型鏈有一定程度的瞭解。
不瞭解可以去先閱讀我另一篇文章,裡面對原型鏈有一個較為詳細的說明:JavaScript 原型鏈詳解。
如果已經瞭解請繼續。
之前寫過一篇博文將繼承方式全部列出來瞭,不過我發現一口氣看完過於長瞭,也不利於吸收知識,所以我先將組合繼承部分劃分出來,後續會把寄生部分補上。
2、原型鏈繼承
父類實例作為子類的原型
子類創造的兩個實例的隱式原型__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);
特點:
- 兩個實例對象都沒有
fatherAttr
屬性,但是因為父類的實例會擁有fatherAttr
屬性,且現在父類的實例作為child的原型,根據原型鏈,他們可以共享到自己的構造函數child
的原型上的屬性。(測試1) - 因為隻有一個父類的實例作為他們的原型,所以所有實例共享瞭一個原型上的屬性
fatherAttr
,當原型上的屬性作為引用類型時,此處是數組,test1
添加一個新內容會導致test2
上的fatherAttr
也改變瞭。(測試2)(缺點) child
構造函數不能傳遞入參。(缺點)- 實例可以訪問到父類的原型上的屬性,因此可以把可復用方法定義在父類原型上。(測試3)
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);
特點:
- 兩個實例對象都擁有瞭拷貝來的
fatherAttr
屬性,所以沒有共享屬性,創造一個實例就得拷貝一次父類的所有屬性,且因為不能繼承父類原型,所以方法不能復用,被迫拷貝方法。(測試1)(缺點) test1
添加一個新內容隻是改變瞭test1
自己的屬性,不會影響到test2
。(測試2)child
構造函數可以傳遞參數,定制自己的屬性。(測試1)- 實例不能繼承父類的原型上的屬性。(測試3)(缺點)
4、組合繼承
結合原型鏈繼承和構造函數繼承,可以根據兩種繼承特點進行使用。
//父類 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);
特點:
- 兩個實例對象都擁有瞭拷貝來的
fatherAttr
屬性,創造一個實例就得拷貝一次父類的所有屬性(構造函數繼承特點,測試1),但是能訪問父類原型,可以把復用方法定義在父類原型上。(原型鏈繼承特點,測試1) test1
添加一個新內容隻是改變瞭test1自己的屬性,不會影響到test2。(構造函數繼承特點,測試2)child
構造函數可以傳遞參數,定制自己的屬性。(構造函數繼承特點,測試1)- 實例能繼承父類的原型上的屬性。(原型鏈繼承特點,測試3)
- 調用瞭兩次父類的構造函數,生成兩份實例,創建子類原型鏈一次,用子類創建實例時,子類內部裡面一次,第二次覆蓋瞭第一次。(缺點)
- 因為調用兩次父類構造函數,如果用delete刪除實例上拷貝來的
fatherAttr
屬性,實例仍然擁有隱式原型指向的父類實例上的fatherAttr
屬性。(原型鏈繼承特點,測試4)(缺點)
到此這篇關於JavaScript組合繼承詳解的文章就介紹到這瞭,更多相關JavaScript組合繼承內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 如何利用JavaScript 實現繼承
- Javascript 原型與原型鏈深入詳解
- JavaScript基礎之函數詳解
- JavaScript進階教程之非extends的組合繼承詳解
- JavaScript三大重點同步異步與作用域和閉包及原型和原型鏈詳解