继承

1:原型链基本概念及原型链继承方法

1.1 理解一下实例,原型,构造函数的关系

 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 的根本原因。同样,这也是

  • 第三句:而实例都包含一个指向原型对象的内部指针

在实例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:借用构造函数实现继承

  顾名思义,就是通过借用超类的构造函数实现继承,这种继承较为简单,还可以解决使用原型链继承遗留的两个问题。当你准备继承的超类的属性和方法都在其构造函数内定义时比较适合使用这种方式继承(一般我们都把属性定义在构造函数内,方法定义在原型上提高代码利用率),因为这个方法的缺点是继承不到超类原型上的东西。

 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图

3:组合继承(原型链+借用)

  鉴于上面两种继承方式,组合继承融合了它们的优点,利用原型链继承继承prototype上的属性和方法,利用借用构造函数继承构造函数内的属性和方法。

 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的那两个属性多余了(毕竟当初是为了补充借用构造函数继承无法继承超类原型上的属性和方法这个缺陷而引入原型链继承:()。

  !组合继承->在父类原型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:寄生组合继承
优势:解决了组合继承两次调用超类构造函数的问题

    

 
 
 
 
 
 
原文地址:https://www.cnblogs.com/Flychown/p/6183178.html