我一直在尝试原型继承,如下面的代码片段所示。
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
的特定实例上。 / p>
调用c.say
时,会发生以下情况:
如果直接在say
上设置了c
,请将其调用。它永远不会直接在实例上设置,因此,请跳至2.
在say
中查找Children.prototype
,f
的实例。再次,直接在f
的实例上查找属性集。如果该行取消注释,则会在此处找到say
,搜索将停止。
在say
中查找f.prototype
,即Parent.prototype
。如果该行仍然已评论,则会找到say
。
问:我是否正确理解JavaScript如何查找该属性?如果是这种情况,则可以解释为什么在取消注释该行时,父属性不会覆盖子属性 。
答案 0 :(得分:1)
我是否正确理解JavaScript如何查找该属性?
基本上,是的。但重要的是要注意,对象的底层原型由new
操作设置为指向构造函数的prototype
属性引用的对象,此时如果指向构造函数{{1在一个完全不同的对象的属性,它不会对现有的子项产生任何影响。孩子们指的是对象,而不是财产。
所以一般来说,属性查找的工作原理如下:
让我们在那里建造一些ASCII-Art,只是为了好玩:
+-----------+ | Parent |<---------------------------+ +-----------+ | | prototype |---------->+-------------+ | +-----------+ +--->| (object) | | | +->+-------------+ | | | | constructor |--+ +------------------+ | | | say |--------->| (function) | | | +-------------+ +------------------+ | | | alert("parent"); | | | +------------------+ | | | +--------------------------------------------------+ | | +-----------------------+ | +-----------+ | | | Child |<---------------------------+ | | +-----------+ +-------------+ | | | | prototype |------+--->| (object) | | | | +-----------+ | +-------------+ | | | | | constructor |--+ | | | | __proto__ |----+ +------------------+ | | | say |--------->| (function) | | | +-------------+ +------------------+ | | | alert("child"); | | +-----------+ | +------------------+ | | c | | | +-----------+ | | | __proto__ |------+ | +-----------+ | | +-----------+ | | p | | +-----------+ | | __proto__ |-----------------------------------------------------------+ +-----------+
...其中prototype
是隐藏属性,表示对象与其原型的链接。 (有些引擎实际上暴露了这个,并且有人建议将其添加到标准中。)
正如您所看到的,__proto__
和Child.prototype
实例的c
都指向同一个对象(同样适用于__proto__
和Parent.prototype
的{ {1}})。
我在上面第一段中作出区分的原因是:
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"
最终会有完全不同的原型,到上述结束时,f1
的{{1}}不再引用同一个对象{{ 1}}指的是。
出于这个原因,除非在特定情况下(例如您的f2
函数),我强烈建议不将新对象分配给构造函数的f1
属性,因为它可能会让人感到困惑。 :-)你的__proto__
函数再次成为例外。