繼承

1:原型鏈基本概念及原型鏈繼承方法

1.1 理解一下實例,原型,構造函數的關係chrome

 1 //構造函數
 2 function father(){
 3     this.fatherName = "father";
 4 }
 5 //原型對象
 6 father.prototype.getFatherName = function(){
 7     alert(this.fatherName);
 8 }
 9 //實例
10 var father_1 = new father();
View Code
  •  第一句:每一個構造函數都有一個原型對象
在chrome中能夠看見,構造函數father中有一個原型對象(prototype)
  • 第二句:原型對象都包含一個指向構造函數的指針

  在原型對象(prototype)中,constructor指針指向的內容就是構造函數自己。由於構造函數又有一個原型對象,因此「構造函數->原型對象->constructor->構造函數」 循環嵌套。這也是對於任意的構造函數F,F.prototype.constructor === F 的根本緣由。一樣,這也是ide

  • 第三句:而實例都包含一個指向原型對象的內部指針

在實例father_1中,內部指針__proto__ 和構造函數的prototype的指向是同樣的,都指向原型對象。
關係圖:
1.2 繼承實現原理
繼承關係圖:
1.3 原型鏈繼承時的注意點
  • 全部繼承的根是Object,這也就是任何對象都會有toString()或者valueOf()方法的緣由。
  • child繼承了father以後,child本身的原型就被替換爲father的實例,因此在繼承以前,child原型上定義的屬性或方法都消失了,所以,應該在繼承以後向child 的原型添加屬性或方法。
 1 function Father()
 2 {
 3     this.FatherName = "father";
 4 }
 5 Father.prototype.GetFatherName = function()
 6 {
 7     alert(this.FatherName);
 8 }
 9 function Child(name)
10 {
11     this.ChildName = name;
12 }
13 //Child原始的prototype上定義GetChildName
14 Child.prototype.GetChildName = function()
15 {
16     alert(this.ChildName);
17 }
18 var child_2 = new Child("child2");
19 child_2.GetChildName(); //child2
20 
21 //繼承 Child原始的prototype被Father的實例覆蓋
22 Child.prototype = new Father();
23 var child_1 = new Child("child1");
24 child_1.GetFatherName();//father
25 child_1.GetChildName();//GetChildName is not a function
View Code
  • 在child繼承father以後,不可使用原型對象字面量的方式向child原型添加屬性和方法,原型對象字面量方式會讓child的原型直接指向Object的實例,以前的繼承會被切斷。
 1 function Father(name)
 2 {
 3     this.FatherName = name;
 4 }
 5 Father.prototype.GetFatherName = function()
 6 {
 7     alert(this.FatherName);
 8 }
 9 function Child(name)
10 {
11     this.ChildName = name;
12 }
13 //繼承
14 Child.prototype = new Father("father");
15 //對象字面量方式重寫了prototype
16 Child.prototype = {
17     sayHello: function()
18     {
19         alert("Hello");
20     }
21 };
22 var child_1 = new Child("child1");
23 child_1.sayHello();
24 child_1.GetFatherName();//GetFatherName is not a function
View Code
1.4 原型鏈繼承的缺陷
  •  由於引用類型值的屬性會被全部實例共享,因此若father中有屬性是引用類型值,child繼承father後,全部的child實例都會共享同一個引用類型屬性。
 1 function Father(name)
 2 {
 3   this.FatherName = name;
 4   this.array = new Array(1,2,3,4);
 5 }
 6 
 7 //Father 原型上定義了些方法
 8 Father.prototype = {
 9   SetFatherArray : function(value)
10   {
11      this.FatherName = value;
12      this.friends.push(value);
13   }, 
14   GetFatherArray : function()
15   {
16      alert(this.array.join("/"));
17   },
18   AddFatherArray : function(value)
19   {
20      this.array.push(value);
21   },
22   friends: new Array("Bob")
23 };
24 
25 function Child(childName)
26 {
27    this.ChildName = childName;
28 }
29 
30 Child.prototype = new Father("father"); 
31 Child.prototype.constructor = Child;
32 var child_1 = new Child("child1");
33 var child_2 = new Child("child2");
34 child_1.AddFatherArray("5");
35 
36 child_1.GetFatherArray();// 1/2/3/4/5
37 child_1.SetFatherArray("chown");
38 alert(child_1.FatherName);// chown
39 
40 child_2.GetFatherArray();// 1/2/3/4/5 實例中引用類型this.array被子類實例共享
41 alert(child_2.FatherName);// father //非引用類型不共享
42 alert(child_2.friends);// Bob,chown  原型中引用類型friends被子類實例共享
43 //Child prototype 繼承的是一個Father實例,
44 //因此任何Child 實例都共享自一個Father實例
45 //所以Father中引用類型(無論是在實例中仍是在原型中的)都將被共享
View Code
  •  在建立child實例時沒法向father的構造函數傳遞參數。

1.5 原型鏈繼承chrome圖函數

2:借用構造函數實現繼承this

  顧名思義,就是經過借用超類的構造函數實現繼承,這種繼承較爲簡單,還能夠解決使用原型鏈繼承遺留的兩個問題。當你準備繼承的超類的屬性和方法都在其構造函數內定義時比較適合使用這種方式繼承(通常咱們都把屬性定義在構造函數內,方法定義在原型上提升代碼利用率),由於這個方法的缺點是繼承不到超類原型上的東西。spa

 1 function Father(name)
 2 {
 3   this.FatherName = name;
 4   this.array = new Array(1,2,3,4);
 5 }
 6 
 7 //Father 原型上定義了些方法
 8 Father.prototype = {
 9   GetFatherArray : function()
10   {
11      alert(this.array.join("/"));
12   },
13   AddFatherArray : function()
14   {
15      this.array.push("Add");
16   },
17   friends: new Array("Bob")
18 };
19 
20 function Child(childName,fatherName)
21 {
22    this.ChildName = childName;
23    Father.call(this,fatherName);//繼承 能夠向父類傳參(1)
24 }
25 
26 var child_1 = new Child("child1","father1");
27 var child_2 = new Child("child2","father2");
28 var father_1 = new Father("father");
29 child_1.array.push("Add");
30 alert(child_1.array);// 1,2,3,4,Add
31 child_1.GetFatherArray()// ERROR
32 alert(child_2.array);// 1,2,3,4  父對象的引用類型再也不被全部子對象共享(2)
33 child_2.GetFatherArray()// ERROR
View Code

2.1 借用構造函數繼承chrome圖prototype

3:組合繼承(原型鏈+借用)指針

  鑑於上面兩種繼承方式,組合繼承融合了它們的優勢,利用原型鏈繼承繼承prototype上的屬性和方法,利用借用構造函數繼承構造函數內的屬性和方法。code

 1 function Father(name)
 2 {
 3   this.FatherName = name;
 4   this.array = new Array(1,2,3,4);
 5 }
 6 
 7 //Father 原型上定義了些方法
 8 Father.prototype = {
 9   GetFatherArray : function()
10   {
11      alert(this.array.join("/"));
12   },
13   AddFatherArray : function(value)
14   {
15      this.array.push(value);
16   },
17   friends : new Array("Bob")
18 };
19 
20 function Child(childName,fatherName)
21 {
22    this.ChildName = childName;
23    Father.call(this,fatherName);//調用Father構造函數(2)
24 }
25 
26 Child.prototype = new Father("father"); //調用Father構造函數(1)
27 Child.prototype.constructor = Child;
28 var child_1 = new Child("child1","father1");
29 var child_2 = new Child("child2","father2");
30 child_1.AddFatherArray("5");
31 child_1.friends.push("alex");
32 
33 child_1.GetFatherArray();// 1/2/3/4/5
34 alert(child_1.FatherName);// father1
35 
36 child_2.GetFatherArray();// 1/2/3/4
37 alert(child_2.FatherName);// father2
38 alert(child_2.friends);// Bob,alex 父類原型上定義的引用類型屬性仍然被全部子類實例共享
View Code

   對於語句 Child.prototype.constructor = Child; 我目前的理解是,由於在繼承Father時Child的prototype被Father的實例覆蓋,這裏從新賦值constructor是爲了保持prototype的完整性。對象

  並且,組合繼承還會調用兩次Father的構造函數,第一次Child.prototype會獲得兩個屬性:FatherName,array.第二次在Child構造函數中,當建立Child實例時,實例會獲得兩個屬性:FatherName,array.Child實例中的屬性會屏蔽在Child原型上的兩個屬性。實際使用中Child.prototype上那兩個屬性根本訪問不到,會被實例上同名的屬性攔截,這樣一來就會顯得在Child.prototype的那兩個屬性多餘了(畢竟當初是爲了補充借用構造函數繼承沒法繼承超類原型上的屬性和方法這個缺陷而引入原型鏈繼承:()。blog

  !組合繼承->在父類原型prototype上定義的引用類型屬性仍然會被全部子類實例共享(通常不推薦在原型上定義屬性)

3.1 組合繼承chrome圖

4:寄生組合繼承

  針對上面組合繼承的缺點,無非是在原型鏈繼承的時候繼承的是父類的實例(實例包含了構造函數和原型prototype),其實構造函數內屬性的繼承用借用構造函數繼承就實現了,就只須要父類原型的一個副本,而後將這個副本指給子類的原型就能夠了。

 1 function inherit(child,father)
 2 {
 3   //var Prototype = Object.create(father.prototype);
 4   var Prototype = father.prototype;
 5   Prototype.constructor = child;
 6   child.prototype = Prototype;
 7 }
 8 
 9 function Father(name)
10 {
11   this.FatherName = name;
12   this.array = new Array(1,2,3,4);
13 }
14 
15 //Father 原型上定義了些方法
16 Father.prototype = {
17   GetFatherArray : function()
18   {
19      alert(this.array.join("/"));
20   },
21   AddFatherArray : function()
22   {
23      this.array.push("Add");
24   },
25 };
26 
27 function Child(childName,fatherName)
28 {
29    this.ChildName = childName;
30    Father.call(this,fatherName);//繼承父類構造函數內的屬性
31 }
32 
33 inherit(Child,Father);
34 
35 var child_1 = new Child("child_1","father_1");
36 var child_2 = new Child("child_2","father_2");
37 
38 child_1.AddFatherArray();
39 child_1.GetFatherArray();// 1/2/3/4/Add
40 
41 child_2.GetFatherArray();// 1/2/3/4
View Code

4.1 寄生組合繼承chrome圖

5 總結

5.1:原型鏈繼承方式
優點:這是最初的繼承方式,實現容易
缺點:超類中的引用類型屬性會被子類共享(多層繼承會形成更大問題),沒法向超類構造函數傳參
特色:已經規範化,使用Object.create()方法實現
5.2:借用 構造函數式繼承
優點:避免了超類引用類型被共享的問題,實現容易
缺點:只能繼承超類構造函數中的屬性和方法,原型(prototype)中的屬性和方法繼承不了,方法或屬性的代碼重用不理想。
5.3:組合繼承方式
優點:原型鏈繼承 + 借用構造函數繼承,發揮了兩個的優點
缺點:這樣超類的構造函數會被執行兩次,實現稍微複雜
5.4:寄生組合繼承
優點:解決了組合繼承兩次調用超類構造函數的問題
相關文章
相關標籤/搜索