JavaScript 中原型继承(prototype-based inheritance)的原理

首先可以运行下下面一段代码看结果:

 1 function RP(){
2 RP.propertyA = "RP.propertyA";
3 this.propertyA = "this.propertyA";
4 }
5 RP.prototype.propertyA = "RP.prototype.propertyA";
6 //test
7 var rp = new RP();
8 console.log(RP.propertyA);
9 console.log(rp.propertyA);//当前有rp.propertyA,所以优先调用此方法
10 delete rp.propertyA;
11 console.log(rp.propertyA);//没有rp.propertyA,则调用rp.__proto__.propertyA;
12 delete RP.propertyA;
13 console.log(rp.propertyA);//删除RP.propertyA无影响
14 //delete RP.prototype.propertyA;
15 //console.log(rp.propertyA);//删除RP.prorotype.propertyA,则结果为undefined

对以上代码的说明:

上面示例中有三个“对象”:

RP:构造函数

RP.prototype:RP的原型

rp:由 new RP()得到的新对象

rp.__proto__ : 一个引用,指向RP.prototype

这三个对象上分别有一个propertyA属性,这三个属性之间没有任何关系。

三个对象的关系是:

RP != rp :因为在js中函数也是对象,而RP和rp就是两个不同的对象

RP.prototype == rp.__proto__;当执行rp = new RP()时,会自动在rp上添加一个属性__proto__指向其构造函数的prototype。

 

了解了三个对象和三个属性的关系,然后再看js查找属性的顺序:

当调用rp.propertyA时,首先在rp下查找,如果找不到,则沿着其原型链__proto__以此向上查找,先找到哪一个就用哪一个,如果都没找到就报错。

 

然后就可以理解为什么能用原型实现继承了:

如果把RP中有一个方法say,把say放在RP.prototype上,则rp=new RP()时,rp.__proto__ == RP.prototype,因此就可以直接使用rp.say方法了。

公有的方法放在prototype上:

由于原型链是引用的而不是clone的,所以如果new rp2 = RP(),那么rp2.say 和 rp.say指向的是同一个对象。所以很适合把方法放在原型上。

私有属性放在this上:

在构造函数中的this指的是rp,因此构造函数中通过this.xxx添加的属性是添加到rp上的,不是公用的,所以属性应该放在构造函数中。

 

 

原型链查找基本概念:

  1. 每一个函数 F 都有一个原型对象(prototype)F.prototype
  2. 每一个函数都可以通过 new 关键字化身成为一个类构造函数,new F 会产生一个对象 O
  3. 在调用对象的某个属性或者方法,比如 O.xxx 的时候,会首先查找对象自身是否有这个方法或者属性,如果没找到就会去对象的构造函数的原型对象中查找(注意有两个定语),也就是查找 O 的构造函数 F 的原型对象 F.prototype.xxx
  4. F.prototype 也是一个对象,查找 F.prototype.xxx 的时候会重复第 3 步的过程




如果还不好理解,找张纸再找跟笔,看下面的实战代码:

var Foo = Function(){};
Foo.prototype = {
   name: 'foo'
};
在纸上画一个框,里面写上 Foo。右边再画一个框写 Foo.prototype。最后右边再画一个框,写 name='foo'。然后画箭头 Foo ----> Foo.prototype ------> name='foo'

var Bar = function(){};在  Foo 下面远一点的地方画个框, 写上 Bar。Bar 右边画个框写 Bar.prototype。画箭头 Bar ----> Bar.prototype

var bar = new Bar();Bar 下面画个框,写上 bar。画箭头 bar ---> Bar。至此,我们创建了两个类 Foo 和 Bar,以及一个 Bar 的实例 bar。Foo 和 Bar 之间还没有任何关系。 

var foo = new Foo();
Bar.prototype = foo;
这段代码将 Foo 和 Bar 联系起来。你可以在 Foo 和 Bar 的中间位置画一个 foo,foo --> Foo。然后在   Bar.prototype 后面画等号 Bar.prototype === foo。原型链就完成了

console.log(bar.name);

  • 查找 bar.name:在执行这段代码时,会按照开始讲的第 3 步中的过程,首先在 bar 自身是否有 name 这个属性(结果没找到),然后查找其构造函数 Bar,然后找到 Bar 的原型对象 Bar.prototype,在这个对象里面找 Bar.prototype.name。
  • 查找 foo.name:此时 Bar.prototype 等价于 foo 对象,此时查找过程等价于 foo.name 的查找。和第三步相同,首先在 foo 自身查找是否有 name 这个属性(结果没找到),然后继续查找 foo 的构造函数  Foo,然后在 Foo.prototype 中查找 name 这个属性。
  • 查找  Foo.prototype.name:因为 Foo.prototype 也是一个对象,因此继续第三步的查找过程。首先在  Foo.prototype 对象自身中查找 name  属性(终于找到了),返回查找到的值 'bar';

整个查找过程的路径:bar--->Bar--->Bar.prototype===foo--->Foo---->Foo.prototype,其中 Foo 和 Bar 都只作为检索 prototype 的中间节点,并不实际参与查找过程。这个路径实际上是 bar ---> Bar.ptototype === foo ---> Foo.prototype,即大家常说的原型链查找。



posted @ 2012-03-06 21:36  妙計出自山人  阅读(414)  评论(0编辑  收藏  举报