实例化对象和引用属性

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相似)。

我已经确定,如果我在构造函数中重新初始化对象属性,就可以避免这个陷阱,像这样:

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

这似乎非常不正规(我需要手动重新初始化属性,但仅当它们是引用对象时)。

有人可以解释一下发生了什么吗?


1
(new Obj).o === (new Obj).o - [继承的] 属性都指向同一个对象,其 x 属性会递增。 - Bergi
“..例如字符串(表现得像数字)和数组(表现得像对象)..” 基本类型是按值复制的(如字符串、数字、布尔值),而对象(是的,array也是一个对象)是按引用复制的。问题在于,当您修改o对象的x属性时,您正在修改原型链中相同o对象的引用。” - gion_13
2个回答

3

可以这样理解。无论值的来源是什么,你始终会修改正在操作的对象。

第一次这样做时:

this.x++;

这里的代码从Obj.prototype.x获取了x的值,因为在this对象上没有x属性。尽管如此,“++”仍然作用于this对象,所以该值被设置在该对象上。该值现在直接存在于实例上,以供将来修改。(prototype.x被隐藏直到直接属性被删除。) 但是,当你这样做时:
this.o.x++
this对象上唯一的操作就是查找o。由于this上没有o属性,所以你将得到存储在Obj.prototype.o中的对象的引用。此时,this对象没有实际修改。
在引用返回后,然后在Obj.prototype.o对象上查找属性x并进行修改。
因此,它实际上非常一致。使用this.o.x++可以对this进行查找,但不进行变异。变异在被引用的对象上。但是使用this.x++,您直接变异了对象。
因此,除非您希望引用类型在从构造函数创建的所有实例之间共享,否则应该将引用类型直接放在实例上,而不是在.prototype上。

谢谢!您证实了我怀疑的事情(尽管您表达得更好)。 - ken

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,那么无论如何都没有关系,因为原型和新属性版本只会指向同一个对象。


1
当然,this.refArr = this.refArr; 会创建一个新的属性,但它指向之前继承的属性所指向的相同数组。您可以通过更改 Obj.prototype.refArr 轻松检查。 - Bergi
啊,你说得对。我在测试中误用了 hasOwnProperty。正在修复。 - Erik Reppen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接