前言
為大家分享js中最常見最詳細的繼承方式,接下來將一下面的幾個維度進行展示說明文章有點長,請耐心閱讀?,有什麼錯誤理解的地方希望留言指出來
- 產生原因
- 程式碼實現
- 基本原理
- 語言實現
- 場景優點
- 缺點
繼承方式
- 原型鏈繼承
- 借用建構函式模式繼承
- 組合繼承
- 原型式繼承
- 寄生式繼承
- 寄生組合
原型鏈繼承
相信小夥伴們都知道到原型鏈繼承(ECMAScript 中描述了原型鏈的概念,並將原型鏈作為實現繼承的主要方法),因為原型鏈繼承非常的強大,但是也有它的缺點,接下來我們們就按照上面的維度看看原型鏈繼承到底是什麼鬼
程式碼實現:(需要兩個建構函式來完成一個原型鏈繼承)
// SuperType 建構函式稱為超類
function SuperType (){
this.name='super';
this.friend=[];
this.property = true;
}
SuperType.prototype.getName=function(){
return this.name;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
// SubType 建構函式稱為子類
function SubType(name,age){
this.name=name;
this.age=age;
this.subproperty = false;
}
SubType.prototype=new SuperType();
SubType.prototype.constrcutor=SubType;
SubType.prototype.getAge=function(){
return this.age;
}
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var child = new SubType('shiny',12);
console.log(child.getName)//shiny
console.log(child.getAge())//12
複製程式碼
圖解部分 屬性
基本原理
使用類似作用域的原型鏈,進行繼承查詢語言實現
定義兩個建構函式,分別為父類(SuperType)、子類(SubType),為了實現子類能夠使用父類的屬性(本身和原型上面的屬性)。重寫子類的原型,讓子類的原型指向父類例項,這樣子類的建構函式就是父類的例項地址,實現子類可以使用父類的本身和原型上的屬性優點
子類可以通過原型鏈的查詢,實現父類的屬性公用與子類的例項
缺點
- 一些引用資料操作的時候會出問題,兩個例項會公用繼承例項的引用資料類
- 謹慎定義方法,以免定義方法也繼承物件原型的方法重名
- 無法直接給父級建構函式使用引數
借用建構函式模式繼承
雖然原型鏈繼承很強大但是也有他的缺點,借用建構函式繼承可以解決原型鏈繼承的缺點,開線面的解釋程式碼實現:
// 把父類當中一個函式使用
function SuperType(name){
this.name=name
this.friend=['a','b']
}
SuperType.prototype.getFriend=function(){
return this.firend
}
function SubType(name){
// 執行父類函式
SuperType.call(this,name);
}
var child = new SubType('shiny')
var childRed = new SubType('red')
console.log(child.name)//shiny
console.log(childRed.name)//red
child.firend.push('c')
console.log(child.friend)//a,b,c
console.log(childRed.friend)//a,b
console.log(childRed.getFriend)//undefined
複製程式碼
基本原理
使用call apply方法,通過執行方法修改tihs (上下文),是的父級的this變成子類例項的this,這樣每個例項都會得到父類的屬性,實現引用屬性備份使用場景
父類中需要一些子類使用共享的引用型別,並且子類可能會操作父類共享的引用型別 但是父類的非this繫結的屬性和方法是不可以使用的(放在父類prototype的屬性和方法)語言實現
不要把父類當中建構函式,當中一個函式來處理這樣更容易理解,在子類的建構函式中借用父類函式通過修改this來執行,這樣子類的例項包含父類的屬性優點
- 解決了原型鏈繼承的 引用型別操作問題
- 解決了父類傳遞引數問題
缺點
- 僅僅使用借用建構函式模式繼承,無法擺脫夠著函式。方法在建構函式中定義複用不可談
- 對於超類的原型定義的方法對於子類是不可使用的,子類的例項只是得到了父類的this繫結的屬性 考慮到這些缺點,單獨使用借用建構函式也是很少使用的
組合繼承
上面的兩種繼承方式(原型鏈繼承+借用建構函式繼承),都有自己優缺點,但是他們不是很完美,下面解釋一下組合繼承程式碼實現:
程式碼實現:
function SuperType(name){
this.name=name;
this.firend=['a','b']
}
SuperType.prototype.getName=function(){
return this.name
}
function SubType(name,age){
this.age=age;
SuperType.call(this,name)
}
SubType.prototype=new SuperType();
SubType.prototype.constrcutor = SubType;
SubType.prototype.getAge=function(){
return this.age
}
var childShiny=new SubType('shiny',23);
var childRed = new SubType('red',22);
childShiny.firend.push('c');
childRed.firend.push('d');
console.log(childShiny.getName());
console.log(childShiny.getAge());
console.log(childRed.getName());
console.log(childRed.getAge());
console.log(childRed.friend);//[a,b,d]
console.log(childShiny.friend);//[a,b,c]
複製程式碼
基本原理
- 使用原型鏈的繼承實現,通過原型查詢功能來滿足原型鏈共享方法
- 使用借用建構函式方法,使用例項備份父類共享引用型別備份
使用場景
得到原型鏈繼承和建構函式繼承的優點,是被開發人員認可的一種繼承方式,但是也有他的缺點語言實現
- 定義兩個建構函式,分別為父類(SuperType)、子類(SubType),為了實現子類能夠使用父類的屬性(本身和原型上面的屬性)。重寫子類的原型,讓子類的原型指向父類例項,這樣子類的建構函式就是父類的例項地址,實現子類可以使用父類的本身和原型上的屬性
- 不要把父類當中建構函式,當中一個函式來處理這樣更容易理解,在子類的建構函式中借用父類函式通過修改this來執行,這樣子類的例項包含父類的屬性
優點
- 解決了原型鏈繼承引用型別的例項操作導致引用改變
- 解決了借建構函式繼承方式的,父類原型子類例項可以使用
缺點
* 父類的建構函式被例項換了兩次 * 例項會有父類的建構函式的一些this屬性、子類的建構函式(prototype)上也有一份例項的上有的屬性原型式繼承
話說上面的的組合繼承不是已經被開發者認可了嗎,原型式繼承是啥?下面我們們看看原型式繼承是什麼樣的。程式碼實現:
程式碼實現:
1 function object(o){
function F(){};
F.prototype=o;
return new F()
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var personShiny = object(person);
var personRed = object(person);
console.log(personShiny.name)//Nicholas
console.log(personRed.name)//Nicholas
personShiny.friends.push('red');
personRed.friends.push('shiny');
console.log(personShiny.friends)//["Shelby", "Court", "Van","red","shiny"]
//ECMAScript 5 通過新增 Object.create()方法規範化了原型式繼承。這個方法接收兩個引數:一
//個用作新物件原型的物件和(可選的)一個為新物件定義額外屬性的物件。在傳入一個引數的情況下,
//Object.create()與 object()方法的行為相同。
2
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var personShiny = Object.create(person);
var personRed = Object.create(person);
console.log(personShiny.name)//Nicholas
console.log(personRed.name)//Nicholas
personShiny.friends.push('red');
personRed.friends.push('shiny');
console.log(personShiny.friends)//["Shelby", "Court", "Van","red","shiny"]
複製程式碼
基本原理
通過Object.create()方法來建立一個有基礎類的例項,這例項的__proto__指向基礎類
使用場景
在不使用建構函式的情況下,只想讓一個物件與另一個物件保持類似的情況下語言實現
需要建立一個基礎物件,作為一個新物件的基礎物件,通過object方法或者Object.create方法處理得到一個新例項,這個新例項上的__proto__指向基礎物件優點
再不用建立建構函式的情況下,實現了原型鏈繼承,程式碼量減少一部分
缺點
- 一些引用資料操作的時候會出問題,兩個例項會公用繼承例項的引用資料類
- 謹慎定義方法,以免定義方法也繼承物件原型的方法重名
- 無法直接給父級建構函式使用引數
寄生繼承
我們們看了上面的原型式繼承,其實就是和原型鏈繼承差別不大,只是省去了建構函式這一部,但是原型式繼承也是有缺點的(不能夠給備份的物件新增屬性),下面寄生繼承來解決。程式碼實現:
程式碼實現:
// 和工廠模式非常類似,建立一個物件,增強一些功能並返回該物件
function createAnother(o){
var clone = Object(o);
clone.sayHi=function(){
console.log('hi')
}
return clone
}
var person = {
name:'shiny',
friends:['a','b']
}
var personShiny = createAnother(person);
console.log(personShiny.sayHi())//Ho
複製程式碼
基本原理
備份一個物件,然後給備份的物件進行屬性新增,並返回
使用場景
在考不使用建構函式的情況下實現繼承,前面示 範繼承模式時使用的 object()函式不是必需的;任何能夠返回新物件的函式都適用於此模式語言實現
類似建構函式,通過一個執行方法,裡面建立一個物件,為該物件新增屬性和方法,然後返回優點
- 再不用建立建構函式的情況下,實現了原型鏈繼承,程式碼量減少一部分
- 可以給備份的物件新增一些屬性
缺點
類似建構函式一樣,建立寄生的方法需要在clone物件上面新增一些想要的屬性,這些屬性是放在clone上面的一些私有的屬性
寄生組合繼承
我們們看了上面的組合繼承看上去已經很完美了,但是也有缺點(父類被例項化兩次、子類例項和子類的建構函式都有相同的屬性),寄生組合就是來解決這些問題的程式碼實現:
程式碼實現:
function inheritPrototype({SubType,SuperType}){
const prototype = Object(SuperType.prototype);
prototype.constrcutor=SubType;
SubType.prototype=prototype;
}
function SuperType(name){
this.name=name;
this.friends=['a','b']
}
SuperType.prototype.getName=function(){
return this.name;
}
function SubType(name,age){
this.age=age;
SuperType.call(this,name)
}
inheritPrototype({SubType,SuperType});
SubType.prototype.getAge=function(){
return this.age
}
var SubTypeShiny = new SubType('Shiny',23);
SubTypeShiny .friends.push('c')
var SubTypeRed = new SubType('Red',21);
SubTypeRed .friends.push('d')
console.log(SubTypeShiny.getName())//Shiny
console.log(SubTypeShiny.getAge())//22
console.log(SubTypeShiny.friends)//['a','b','c']
console.log( SubTypeRed.getName())//Red
console.log( SubTypeRed.getAge())//21
console.log( SubTypeRed.friends)//['a','b','d']
複製程式碼
基本原理
- 子類建構函式內通過call、apply方法進行修改父類建構函式的this和執行父類建構函式,使的子類的例項擁有父類建構函式的一些屬性,
- 結合子類的原型修改成父類建構函式的原型,並把父類的原型的constructor指向子類建構函式
使用場景
在考不使用建構函式的情況下實現繼承,前面示 範繼承模式時使用的 object()函式不是必需的;任何能夠返回新物件的函式都適用於此模式語言實現
極度類似組合寄生方式,只是修改了子類原型鏈繼承的方式,組合寄生是繼承父類的例項,寄生組合寄生則是通過一子類的原型繼承父類的原型,並把該原型的constructor指向子類建構函式優點
- 在少一次例項化父類的情況下,實現了原型鏈繼承和借用建構函式
- 減少了原型鏈查詢的次數(子類直接繼承超類的prototype,而不是父類的例項)
缺點
暫無
下面是組合繼承和寄生組合繼承的原型圖對比