JS 对象(6)—原型链

接上篇 JS 对象(5)—原型模式

 

原型链

许多OO语言都支持两种继承方法:接口继承和实现继承;接口继承只继承方法签名,而实现继承则继承实际的方法。

在JS中,函数没有签名,所以不能接口继承,只能实现继承;而且其实现继承主要是依靠原型链来实现的。

 

原型链

其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

每个构造函数都有一个原型对象,每个原型对象都包含一个指向构造函数的指针,而实例则包含一个指向原型对象的指针。

假如让A原型对象等于B构造函数的实例,那么A原型对象就会有一个[[Prototype]]指针指向B原型对象,同时B原型对象的constructor属性又指向了B构造函数;假如让B原型对象等于C构造函数的实例,上述关系依然成立;如此层层递进,从而实现了实例与原型之间的链接;这就是原型链的基本概念。

 

默认的原型

原型链的基本思想是让一个构造函数的原型对象等于另一个构造函数的实例。

既然如此,Object构造函数是最顶层的构造函数,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个[[Prototype]]指针,指向Object.prototype;这也是所有自定义类型都会继承Object类型所拥有的实例和方法的根本原因。

 

确认原型和实例之间的关系

instanceof操作符;只要是原型链上出现过的构造函数,都会返回true。

instanceof操作符的本质在于:左边的操作数的原型链上是否有右边的操作数的prototype属性。

isPrototypeOf()方法,只要是原型链上出现过的原型,都会返回true。

示例:

 1 function A(name){
 2     this.name = name;
 3 };
 4 A.prototype.sayName = function(){
 5     alert(this.name);
 6 };
 7 function B(age){
 8     this.age = age;
 9 };
10 B.prototype = new A();
11 B.prototype.sayAge = function(){
12     alert(this.age);
13 };
14 var x = new B();
15 x.name = "CC";
16 
17 console.log(x.sayName());    //"CC"
18 console.log(x instanceof B);    //true
19 console.log(x instanceof A);    //true
20 console.log(x instanceof Object);    //true
21 console.log(Object.prototype.isPrototypeOf(x));    //true
22 console.log(A.prototype.isPrototypeOf(x));    //true
23 console.log(B.prototype.isPrototypeOf(x));    //true

 

谨慎地定义方法

子类型如果需要覆盖超类型中的某个方法,或者需要添加某个超类型中不存在的方法;给原型添加(/重写)方法的代码一定要放在替换原型的语句之后。

即,先让B的原型等于A的实例,再给B添加(/重写)方法。

在重写了方法之后,B的实例调用的是重写之后的方法;但是A的实例调用的还是重写之前的方法。

 

在通过原型链实现继承时,不能使用对象字面量创建原型对象,因为这样做就会重写原型对象;重写之后的原型是一个Object类型的实例,原来的原型链被切断。

 

原型链的问题

(1)由于原型中的所有属性和方法会被实例共享,所以在创建对象时,要在构造函数中定义属性,在原型中定义方法和共享的属性。

在原型链中,由于B原型是A函数的实例,A函数中定义的属性会变成现在的B原型属性,继而被所有B函数的实例共享。

如果这些被所有实例共享的属性中存在包含引用类型值的属性,那就麻烦大了。

(2)在创建子类型的实例时,没有办法在不影响所有对象实例的前提下,给超类型的构造函数传递参数。

 

借用构造函数

借用构造函数(constructor stealing),又称伪造构造函数,或者经典继承

基本思想:在子类型构造函数的内部调用超类型构造函数。

函数只不过是在特定环境中执行代码的对象;因此,通过使用apply()方法和call()方法,可以在(将来)新创建的对象上执行构造函数。 

 

相对于原型链而言,借用构造函数可以在子类型构造函数中向超类型构造函数传递参数。

 1 function A(name){
 2     this.name = name;
 3 };
 4 A.prototype.sayName = function(){
 5    alert(this.name);
 6 };
 7 function B(){
 8     A.call(this,"CC");
 9     this.age = 23;
10 };
11 var x = new B();
12 
13 console.log(x.name);    //"CC"
14 console.log(x.age);    //23
15 console.log(x.sayName);    //报错

 

 

借用构造函数的问题

(1)方法都在构造函数中定义;

(2)在超类型的原型中定义的方法,子类型不可见(子类型不是超类型构造函数的实例,子类型与超类型构造函数、超类型的原型,都不存在链接)。

 

组合继承

又称伪经典继承,将原型链和借用构造函数的技术结合起来;

使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。

示例:

 1 function A(name){
 2     this.name = name;
 3     this.colors = ["red","green","blue"];
 4 };
 5 A.prototype.sayName = function(){
 6    alert(this.name);
 7 };
 8 function B(name,age){
 9     A.call(this,name);    //继承属性
10     this.age = age;
11 };
12 B.prototype = new A();    //继承方法
13 B.prototype.sayAge = function(){
14     alert(this.age);
15 };

 

1 var x = new B("CC",23);
2 x.colors.push("black");
3 console.log(x.name);    //"CC"
4 console.log(x.age);    //23
5 console.log(x.colors);    //"red", "green", "blue", "black"
6 
7 var y = new B("VV",32);
8 console.log(y.colors);    //"red", "green", "blue"

 

instanceof操作符和isPrototypeOf()方法都能识别基于组合继承创建的对象。

1 console.log(y instanceof B);    //true
2 console.log(y instanceof A);    //true
3 console.log(y instanceof Object);    //true  
4 console.log(B.prototype.isPrototypeOf(y));    //true
5 console.log(A.prototype.isPrototypeOf(y));    //true
6 console.log(Object.prototype.isPrototypeOf(y));    //true

 

posted @ 2016-07-28 21:17  Aaron_Xiao  阅读(461)  评论(1)    收藏  举报