简体   繁体   中英

When is a property created on the current object VS a prototype?

I was reading Google's JS optimization page , and got to this section:

Place instance variable declaration/initialization on the prototype for instance variables with value type... This avoids unnecessarily running the initialization code each time the constructor is called.

I thought, "wait, then you can't change the values in your instance"

But turns out you can, and that surprised me.

Why, when you change a value provided to you by a prototype, does it get set on the object itself?

x = { one: 1 }
y = Object.create(x)
y.one // 1, of course
y.one++;
y.one // 2
x.one // 1! why isn't this 2?

What is the rule that defines this behavior?

Is it that a Left Hand Side lookup is always on the nearest object, but a Right Hand Side lookup will traverse the prototype chain?

If so, this would apply to both reference and value types, but perhaps the temptation to mutate ( y.arr.push(1) ) would be too much, hence Google's recommendation to do this only for values?

It is a bit more complex.

Right hand side: look up the prototype chain, find the first one that matches (whether getter or data property), or return undefined if none is found

Left hand side: look up the prototype chain, if you find a setter use that; otherwise, set a data value on the object itself.

It would be horrible if simple assignment changed a prototype. (Imagine, for example, the International Prototype Kilogram automagically changing weight if you chip one of your own kilogram weights, and the chaos that would ensue.)

Does this mean an explicit setter only, not just any ol' editable property? Could I trouble you to ask for an example that has a setter higher up the prototype and an example that does not?

 var x = { data: 1, hidden: 1, set accessor(val) { this.hidden = val; }, get accessor() { return this.hidden; } }; var y = Object.create(x); console.log("orig: x.data:", x.data, "; y.data:", y.data); // 1, 1 y.data = y.data + 1; console.log("inc: x.data:", x.data, "; y.data:", y.data); // 1, 2 delete y.data; // removes y.data, so y.data is again looked up at x.data console.log("del data: x.data:", x.data, "; y.data:", y.data); // 1, 1 console.log("----------"); console.log("orig: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 1 y.accessor = y.accessor + 1; console.log("inc: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 2 console.log(" x.hidden:", x.accessor, "; y.hidden:", y.hidden); // 1, 2 delete y.accessor; // silent fail, y.accessor doesn't exist console.log("del acc: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 2 console.log(" x.hidden:", x.hidden, "; y.hidden:", y.hidden); // 1, 2 delete y.hidden; console.log("del hidden: x.accessor:", x.accessor, "; y.accessor:", y.accessor); // 1, 1 console.log(" x.hidden:", x.hidden, "; y.hidden:", y.hidden); // 1, 1 

The difference you can see demonstrated here is that we can delete y.data (data property) after y.data = ... , but we cannot delete y.accessor after y.accessor = ... , since it doesn't exist -- x.accessor was used to set y.hidden .

That's because the property assignment (the [[Set]] internal method) reaches the property on the prototypical object x , but the receiver is still y . Then, the property is defined on y . The property on x remains unaltered.

More technically, the assignment y.one = 2 does this:

  1. y.[[Set]]("one", 2, y) is called.
  2. Since y has no "one" own property, the call is redirected to the parent y.[[GetPrototypeOf]]() , which is x .
  3. x.[[Set]]("one", 2, y) is called.
  4. Since x has a "one" own property which is a data property (ie not a getter/setter accessor) and is writable, a new property is defined on y
  5. y.[[DefineOwnProperty]]("one", PropertyDescriptor{[[Value]]: 2})

If you don't want that, you can use custom accessors:

 var one = 1; var x = { get one() { return one; }, set one(value) { one = value; } }; var y = Object.create(x); console.log(y.one); // 1 y.one++; console.log(y.one); // 2 console.log(x.one); // 2 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM