Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instanced object and properties that are references

Tags:

javascript

Just when I thought I had JS figured out, I get hung up on this:

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

Expected:

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

Actual:

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

So, it seems that if a prototypal property is a reference type, then it is shared across all instances, yet if it's a non-reference type then it is reinitialized for each instance; to confirm this hypothesis, I tested a few other types, such as string (which behaves like number) and array (which behaves like object).

I have determined that I can avoid this pitfall if I reinitialize the object property in the ctor, like this:

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

Which just seems really unorthodox (that I'd be required to manually reinitialize properties but only if they're a reference object).

Can anyone shed some light on what's going on?

like image 606
ken Avatar asked Oct 22 '22 05:10

ken


1 Answers

Think of it this way. You're always going to be modifying the object on which you're operating, irrespective of where the value comes from.

When you do this for the first time:

this.x++;

It is getting the value of x from Obj.prototype.x because there was no x property directly on the this object, nevertheless the ++ is still operating on the this object, so the value is set on that object. That value is now directly on the instance for future modifications. (The prototype.x is shadowed until the direct property is deleted.)

However, when you do this:

this.o.x++

The only operation on the this object is the lookup of o. Since again there is no o property on this, you'll get the reference to the object stored at Obj.prototype.o. At this point, there's no actual modification of the this object.

After the reference is returned, you then look up the property x on the Obj.prototype.o object, and modify it.

So it's actually quite consistent. With this.o.x++ you've performed a lookup on this, but no mutation. The mutation is on the referenced object. But with this.x++, you're mutating the object directly.


So yes, unless you want your reference types to be shared among all instances created from the constructor, you should put the reference types directly on the instances and not on the .prototype.

like image 194
I Hate Lazy Avatar answered Nov 01 '22 18:11

I Hate Lazy