2013-09-27 76 views
0

我一直在试验原型继承,如下面的代码片段所示。在父级原型上设置属性

function extend(c, p) { 
    function f() { this.constructor = c; } 
    f.prototype = p.prototype; 
    c.prototype = new f(); 
} 

function Parent() {} 
function Child() {} 

extend(Child, Parent); 

var p = new Parent(); 
var c = new Child(); 

// Child.prototype.say = function() { alert("child"); }; 
Parent.prototype.say = function() { alert("parent"); }; 

p.say(); 
c.say(); 

运行此脚本时,会显示两条警报显示parent

但是,如果我取消对注释行的注释,则第一个警报显示parent,而第二个显示child
乍一看,这是非常意外的。看起来Parent.say覆盖Child.say,因为它稍后设置。

从我的理解,因为Child.prototype是一个对象,而f一个实例,在这个原型设置的所有属性上直接设置的f该特定实例的实例。

当调用c.say,将发生以下情况:

  1. 如果say直接设置c,调用它。它不会直接在实例上设置,因此,请跳至2.

  2. Children.prototype中查找sayf的实例。再次查找直接在f实例上设置的属性。如果该行是未注释,则在此处找到say,并停止搜索。

  3. 查找sayf.prototype,这是Parent.prototype。这是发现say的地方,当时线路仍然为评论了

问:我才明白正确的JavaScript如何查找财产?如果是这种情况,那么可以解释为什么子行不是,如果该行未被注释,则由父属性重写

回答

1

我是否正确理解JavaScript如何查找属性?

基本上,是的。但重要的是要注意,对象的基础原型由new操作设置为指向由构造函数的prototype属性引用的对象,此时如果您将构造函数的prototype属性指向完全不同的对象,则它不会“对已有的孩子没有任何影响。孩子们是指物体,而不是物体。

所以一般来说,财产查找工作原理是这样:

  • 是否属性的对象本身存在吗?如果是这样,请使用它的值。
  • 不,该属性是否存在于该对象的基础原型上?如果是这样,请使用它的值。
  • 它的原型的原型上是否存在?如果是这样,请使用该值。
  • 依此类推,直到我们找到属性,或用完原型。

让我们把一些ASCII艺术在你构建有什么,只是为了好玩:

+-----------+ 
| Parent |<---------------------------+ 
+-----------+       | 
| prototype |---------->+-------------+ | 
+-----------+  +--->| (object) | | 
        | +->+-------------+ | 
        | | | constructor |--+  +------------------+ 
        | | | say   |--------->| (function) | 
        | | +-------------+   +------------------+ 
        | |       | alert("parent"); | 
        | |       +------------------+ 
        | | 
        | +--------------------------------------------------+ 
        |             | 
        +-----------------------+       | 
+-----------+        |       | 
| Child  |<---------------------------+ |       | 
+-----------+   +-------------+ | |       | 
| prototype |------+--->| (object) | | |       | 
+-----------+  | +-------------+ | |       | 
        | | constructor |--+ |       | 
        | | __proto__ |----+  +------------------+ | 
        | | say   |--------->| (function) | | 
        | +-------------+   +------------------+ | 
        |        | alert("child"); | | 
+-----------+  |        +------------------+ | 
|  c  |  |             | 
+-----------+  |             | 
| __proto__ |------+             | 
+-----------+               | 
                     | 
+-----------+               | 
|  p  |               | 
+-----------+               | 
| __proto__ |-----------------------------------------------------------+ 
+-----------+

...其中__proto__是表示对象到它的原型链接的隐藏属性。对于Parent.prototype(有些发动机实际上暴露这一点,并有将它添加到标准的建议。)

正如你所看到的,Child.prototypec实例的__proto__都指向同一个对象(类似与p__proto__ )。

我之所以让我在上面第一款的区别是这样的:

function Foo() { 
} 
Foo.prototype.a = 1; 
var f1 = new Foo(); 
Foo.prototype = {b: 2}; // Putting an entirely new object on `prototype`, not just modifying it 
var f2 = new Foo(); 
console.log(f1.a); // "1" 
console.log(f1.b); // "undefined" 
console.log(f2.a); // "undefined" 
console.log(f2.b); // "2" 

f1f2最终不得不完全不同的原型,并通过上述的结束,f1__proto__不再指到同一个对象Foo.prototype是指。

出于这个原因,除非是非常特殊的情况下(如您的extend功能),我强烈建议分配新对象的构造函数的prototype属性,因为它可以变得很混乱。 :-)你的extend函数也是一个例外。

+0

有道理。现有实例受到影响的原因是实例的原型和构造函数的原型仍然保持相同的对象。 – afsantos

+0

@afsantos:对,它们都指向同一个对象,因此通过这两个引用向该对象添加属性。 –

+0

非常棒的答案!对于ASCII艺术的奖励点,应该有一个工具来自动说明这些事情。 – afsantos