史上最為詳細的javascript繼承

zhangfaliang發表於2019-05-16

前言

        為大家分享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
複製程式碼

圖解部分 屬性

史上最為詳細的javascript繼承

基本原理

使用類似作用域的原型鏈,進行繼承查詢

語言實現

        定義兩個建構函式,分別為父類(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,而不是父類的例項)

缺點

暫無

下面是組合繼承和寄生組合繼承的原型圖對比

史上最為詳細的javascript繼承

相關文章