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();
- 第一句:每个构造函数都有一个原型对象

- 第二句:原型对象都包含一个指向构造函数的指针
在原型对象(prototype)中,constructor指针指向的内容就是构造函数本身。因为构造函数又有一个原型对象,所以“构造函数->原型对象->constructor->构造函数” 循环嵌套。这也是对于任意的构造函数F,F.prototype.constructor === F 的根本原因。同样,这也是
- 第三句:而实例都包含一个指向原型对象的内部指针



- 所有继承的根是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
- 在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
- 因为引用类型值的属性会被所有实例共享,所以若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中引用类型(不管是在实例中还是在原型中的)都将被共享
- 在创建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
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 父类原型上定义的引用类型属性仍然被所有子类实例共享
对于语句 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
4.1 寄生组合继承chrome图
5 总结