5

就在我以为我已经弄清楚了 JS 时,我被挂断了:

function Obj() {
    console.log('x: %s, o.x: %s', this.x++, this.o.x++);
}    
Obj.prototype.x = 1;
Obj.prototype.o = {x: 1};

预期的:

> new Obj
x: 1, o.x: 1
> new Obj
x: 1, o.x: 1
> new Obj
x: 1, o.x: 1

实际的:

> new Obj
x: 1, o.x: 1
> new Obj
x: 1, o.x: 2
> new Obj
x: 1, o.x: 3

因此,似乎如果原型属性是引用类型,那么它在所有实例之间共享,但如果它是非引用类型,那么它会为每个实例重新初始化;为了证实这个假设,我测试了其他一些类型,例如string(其行为类似于number)和array(其行为类似于object)。

我已经确定,如果我重新初始化 ctor 中的对象属性,我可以避免这个陷阱,如下所示:

function Obj() {
    this.o = {x: 1};
}

这看起来真的很不正统(我需要手动重新初始化属性,但前提是它们是参考对象)。

任何人都可以阐明发生了什么吗?

4

2 回答 2

3

这样想吧。无论值来自何处,您都将始终修改您正在操作的对象。

当您第一次这样做时:

this.x++;

x它正在获取from的值,Obj.prototype.x因为没有x直接在this对象上的属性,但是++仍在对对象进行操作this,因此在该对象上设置了值。该值现在直接在实例上以供将来修改。prototype.x在直接属性为deleted 之前,一直被遮蔽。)

但是,当您这样做时:

this.o.x++

this对象的唯一操作是查找o. 由于在 上再次没有o属性this,您将获得对存储在的对象的引用Obj.prototype.o。此时,没有对this对象进行实际修改。

返回引用后,然后查找对象的属性xObj.prototype.o并对其进行修改。

所以它实际上是相当一致的。随着this.o.x++您对 进行了查找this,但没有突变。突变在引用的对象上。但是使用this.x++,您正在直接改变对象。


所以是的,除非您希望在从构造函数创建的所有实例之间共享引用类型,否则您应该将引用类型直接放在实例上,而不是放在.prototype.

于 2012-10-18T18:21:28.077 回答
0

我认为第一个答案有它的要点,但我从一个稍微不同的角度回答。

理解它的关键是关于分配给对象的对象行为。

如果没有实例属性,则查找回退到原型。总是。

但是,分配每次都会自动创建一个新的实例属性。如果您将按引用原型属性分配给新的实例属性,这并不重要,因为它们都只是指向同一事物的指针。如果您就地变异,也不会创建新的实例属性。

function Obj(){
    //this.refArr resolves to what's in the prototype and that's what we change
    this.refArr.push(1);

    //now copy sliceArray and assign a new but identical array
    this.sliceArray = this.sliceArray.slice(0);

    //no length increase would mean a new instance property is created
    //... and then the assigment took place.
    this.sliceArray.push(1);
    console.log( 'ref:'+this.refArr.length +', sliceArray:'+this.sliceArray.length);
}
Obj.prototype.refArr = [];
Obj.prototype.sliceArray = [];
new Obj; new Obj;
//ref:1, sliceArray:1
//ref:2, sliceArray:1

这本质上就是你所做的事情的不同之处。this.x = this.x + 1创建一个新属性。由于您分配给一个属性,this.constructor.prototype.o因此没有创建新属性。如果您递增然后将 this.o 分配给 this.o 无论如何都没关系,因为原型和新属性版本只会指向同一个对象。

于 2012-10-18T18:48:25.247 回答