继承

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:寄生组合继承
优点:解决了组合继承两次调用超类构造函数的问题
相关文章
相关标签/搜索