就在我以为我有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中的object属性,我已经确定可以避免这个陷阱,如下所示:
function Obj() {
this.o = {x: 1};
}
这似乎非常不正统(我需要手动重新初始化属性,但前提是它们是参考对象)。
任何人都可以了解正在发生的事情吗?
答案 0 :(得分:3)
这样想。无论价值来自何处,您总是会修改您正在操作的对象。
第一次执行此操作时:
this.x++;
从x
获取Obj.prototype.x
的值是因为x
对象上没有this
属性,但++
仍然是<{1}} em>在<{1}}对象上运行,因此在该对象上设置了值。该值现在直接在实例上以供将来修改。 (this
被遮蔽,直到直接属性为prototype.x
d。)
但是,当你这样做时:
delete
this.o.x++
对象上的唯一操作是查找this
。由于o
上没有o
属性,您将获得对存储在this
的对象的引用。此时,Obj.prototype.o
对象没有实际修改。
返回引用后,然后在this
对象上查找属性x
,并对其进行修改。
所以它实际上非常一致。使用Obj.prototype.o
,您已对this.o.x++
执行了查找,但没有突变。突变在引用的对象上。但是使用this
,您可以直接改变对象。
是的,除非您希望在从构造函数创建的所有实例之间共享引用类型,否则应将引用类型直接放在实例上而不是this.x++
上。
答案 1 :(得分:0)
我认为第一个答案有它的要点,但我的答案略有不同。
理解它的关键是分配给对象的对象行为。
如果没有实例属性,查找将回退到原型。总是
但是,分配每次都会自动创建一个新的实例属性。如果将by-reference prototyped属性分配给新的实例属性,这并不重要,因为它们只是指向同一事物的指针。如果你就地变异,也不会创建新的实例属性。
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无论如何都无关紧要,因为原型和新属性版本只会指向同一个对象。