前言
首先學習繼承之前,要對原型鏈有一定程度的瞭解。
不瞭解可以去先閱讀我另一篇文章,裡面對原型鏈有一個較為詳細的說明: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);
特點:
- 兩個例項物件都沒有fatherAttr屬性,但是因為父類的例項會擁有fatherAttr屬性,且現在父類的例項作為child的原型,根據原型鏈,他們可以共享到自己的建構函式child的原型上的屬性。(測試1)
- 因為只有一個父類的例項作為他們的原型,所以所有例項共享了一個原型上的屬性fatherAttr,當原型上的屬性作為引用型別時,此處是陣列,test1新增一個新內容會導致test2上的fatherAttr也改變了。(測試2)(
缺點
)- child建構函式不能傳遞入參。(
缺點
)- 例項可以訪問到父類的原型上的屬性,因此可以把可複用方法定義在父類原型上。(測試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)(
缺點
)
組合繼承
結合原型鏈繼承和建構函式繼承,可以根據兩種繼承特點進行使用。
//父類
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)(
缺點
)