- Why is this logging
5
, undefined
, undefined
, 6
instead of undefined
, 6
, undefined
, 6
?
- Why replacing the prototype is not changing the prototype of all the instances of the object, as it would usually do?
Fundamentally, this comes down to the fact that object references are values, rather like numbers, that tell the JavaScript engine (V8 in your case) where the object is in memory. When you copy a value, you do just that: You copy the value. Copying an object reference makes a copy of the reference (not the object), and doesn't in any way tie the destination of that value to the source of the value, anymore than this ties b
to a
:
var a = 5;
var b = a;
a = 6;
console.log(b, a); // 5, 6
And so, your code logs what it logs, and doesn't change instance1
's prototype, for the same reason this code logs that same thing and doesn't change the value of instance1.p
:
var foo = {x: 5};
var instance1 = {p: foo};
foo = {y: 6};
var instance2 = {p: foo};
console.log(instance1.p.x, instance1.p.y, instance2.p.x, instance2.p.y);
Let's throw some ASCII-art (well, Unicode-art) at it, which will also answer:
- What is the V8 engine doing, step by step, in this code?
After you run this:
var obj = function() {};
obj.prototype.x = 5;
var instance1 = new obj();
...you have something roughly like this in memory (ignoring some details):
+−−−−−−−−−−−−−−−−−−−−−+
| (function) |
+−−−−−−−−−−−−−−−−−−−−−+
obj<Ref55461>−−−>| prototype<Ref32156> |−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−−−−+ \
\
\
\ +−−−−−−−−−−+
+−>| (object) |
/ +−−−−−−−−−−+
/ | x<5> |
+−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−+
| (object) | /
+−−−−−−−−−−−−−−−−−−−−−−−−−+ /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
+−−−−−−−−−−−−−−−−−−−−−−−−−+
Since object references are values, and assignment always copies values, when V8 (or any other engine) creates instance1
, it copies the value from obj.prototype
(shown here conceptually as <Ref32156>
) to instance1
's [[Prototype]]
internal slot.
Then, when you do
obj.prototype = {y: 6};
...you're changing the value in obj.prototype
(shown here as changing <Ref32156>
to <Ref77458>
), but that has no effect on the value (<Ref32156>
) in instance1
's [[Prototype]]
slot:
+−−−−−−−−−−−−−−−−−−−−−+
| (function) |
+−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−+
obj<Ref55461>−−−>| prototype<Ref77458> |−−−−−−−−−−−−−−−−−−>| (object) |
+−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−+
| y<6> |
+−−−−−−−−−−+
+−−−−−−−−−−+
+−>| (object) |
/ +−−−−−−−−−−+
/ | x<5> |
+−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−+
| (object) | /
+−−−−−−−−−−−−−−−−−−−−−−−−−+ /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
+−−−−−−−−−−−−−−−−−−−−−−−−−+
...and so consequently, when you do
var instance2 = new obj();
...you have:
+−−−−−−−−−−−−−−−−−−−−−+
| (function) |
+−−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−+
obj<Ref55461>−−−>| prototype<Ref77458> |−−−−−−−−−−−−−−−−+−>| (object) |
+−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−+
/ | y<6> |
+−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−+
| (object) | /
+−−−−−−−−−−−−−−−−−−−−−−−−−+ /
instance2<Ref98465>−−>| [[Prototype]]<Ref77458> |−+ +−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−−−−−−−−+ +−>| (object) |
/ +−−−−−−−−−−+
/ | x<5> |
+−−−−−−−−−−−−−−−−−−−−−−−−−+ / +−−−−−−−−−−+
| (object) | /
+−−−−−−−−−−−−−−−−−−−−−−−−−+ /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
+−−−−−−−−−−−−−−−−−−−−−−−−−+
...which explains the console.log
result.
- EDIT: How would I go about changing the prototype of ALL the instances?
If you want to actually change what object they're using as their prototype, you can only do that if you have a reference to the instance(s) you want to change, and only on a JavaScript engine supporting ES2015 features, by using Object.setPrototypeOf
or (in a web browser environment, and if the object ultimately inherits from Object.prototype
) via the __proto__
accessor property (not recommended):
Object.setPrototypeOf(instance1, obj.prototype);
setPrototypeOf
changes the value of the [[Prototype]]
internal slot in the object (as does setting __proto__
, if the object has it).
You can't do it if you don't have access to the instances.
I don't think you do, given the question, but if you just want to change the state of the object they use as their prototype (perhaps by adding y
), you can of course just set properties on it, and because JavaScript's prototypical inheritance is "live" (there's a live link back to the prototype from an instance), you can then access those properties on any instance that inherits from the prototype, even if they were created before you made the change:
var Example = function() { };
Example.prototype.x = 5;
var instance1 = new Example();
console.log(instance1.x, instance1.y); // 5, undefined
Example.prototype.y = 6;
console.log(instance1.x, instance1.y); // 5, 6