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直接设置为 on c,则调用它。它永远不会直接在实例上设置,因此,请跳至 2。

  2. say在 中查找Children.prototype的实例f。同样,直接在 的实例上查找属性集f。如果该行未注释say则在此处找到,并且搜索停止。

  3. say在 中寻找f.prototype,即Parent.prototypesay当该行保持为commented时,这是可以找到的。

问:我是否正确理解 JavaScript 如何查找属性?如果是这种情况,那就可以解释为什么在取消注释该行时,子属性不会被父属性覆盖。

4

1 回答 1

1

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

本质上,是的。但需要注意的是,对象的底层原型是由new操作设置为指向构造函数的prototype属性所引用的对象,此时如果将构造函数的属性指向prototype完全不同的对象,它不会有任何对现有孩子的影响。孩子指的是对象,而不是属性。

所以一般来说,属性查找是这样工作的:

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

让我们在你正在构建的东西上扔一些 ASCII-Art,只是为了好玩:

+-----------+
| 家长 |<----------------------------+
+-----------+ |
| 原型 |--------->+-------------+ |
+-----------+ +--->| (对象) | |
                   | +->+--------------+ |
                   | | | 构造函数 |--+ +------------------+
                   | | | 说|--------->| (函数) |
                   | | +--------------+ +------------------+
                   | | | 警报(“父母”);|
                   | | +------------------+
                   | |
                   | +-------------------------------------------------- -+
                   | |
                   +------------------------+ |
+-----------+ | |
| 孩子 |<----------------------------+ | |
+------------+ +-------------+ | | |
| 原型 |--------+--->| (对象) | | | |
+-----------+ | +--------------+ | | |
                   | | 构造函数 |--+ | |
                   | | __proto__ |----+ +-----------------+ |
                   | | 说|--------->| (函数) | |
                   | +-------------+ +------------------+ |
                   | | 警报(“孩子”);| |
+-----------+ | +------------------+ |
| c | | |
+-----------+ | |
| __proto__ |------+ |
+-----------+ |
                                                                        |
+-----------+ |
| p | |
+-----------+ |
| __proto__ |------------------------------------------------ ------------+
+-----------+

...__proto__表示对象到其原型的链接的隐藏属性在哪里。(一些引擎实际上公开了这一点,并且有一个建议将其添加到标准中。)

如您所见,Child.prototype实例c__proto__指向同一个对象(对于 and 也是类似Parent.prototypep__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's__proto__不再指代同一个对象Foo.prototype

出于这个原因,除了在非常特殊的情况下(比如你的extend函数),我强烈建议不要将新对象分配给prototype构造函数的属性,因为它会变得非常混乱。:-) 你的extend函数又是一个例外。

于 2013-09-27T10:50:06.417 回答