Reputation: 69276
I ran into a strange snippet of code, which I cannot understand at all, here it is:
var obj = function() {};
obj.prototype.x = 5;
var instance1 = new obj();
obj.prototype = {y: 6};
var instance2 = new obj();
console.log(instance1.x, instance1.y, instance2.x, instance2.y);
// 5, undefined, undefined, 6
Now, the questions are:
5, undefined, undefined, 6
instead of undefined, 6, undefined, 6
?Every explanation is appreciated.
Upvotes: 12
Views: 1250
Reputation: 1074028
- Why is this logging
5
,undefined
,undefined
,6
instead ofundefined
,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
Upvotes: 2
Reputation: 639
Because the instance1
is already created. The new
keyword creates new object by executing a constructor function, which in your case is obj
.
Because you changed the prototype after the initialization of the first instance, you do not have the same state (e.g. the prototype) of the constructor anymore and can't make the same object. But the created ones are still there, referencing to the old prototype.
When you use the obj
constructor again you are making another object which could be very roughly translated to the classical type of inheritance's terminology as instance of another class.
Edit: #4 This fiddle: http://jsfiddle.net/doy3g1fh/ shows that
obj.prototype.y=6
succeeds in changing all existing objects. So the answer obviously is that you shouldn't assign new object as prototype but just modify the current prototype.
Upvotes: 0
Reputation: 239443
According to ECMA Script 5 specifications,
The value of the
prototype
property is used to initialise the[[Prototype]]
internal property of a newly created object before the Function object is invoked as a constructor for that newly created object.
It is clear that prototype
is just to initialize the [[Prototype]]
property. When we create an object, [[Prototype]]
is set as the constructor function's prototype
object and the prototype chain is established. In your case, when you do
var obj = function() {};
obj.prototype.x = 5;
var instance1 = new obj();
the [[Prototype]]
looks like this
console.log(Object.getPrototypeOf(instance1));
# { x: 5 }
(Yes, you can access the [[Prototype]]
with Object.getPrototypeOf
function)
So, when JS Engine looks for x
in instance1
, it finds the value as 5
and since y
is not defined, it uses undefined
.
In the second case,
obj.prototype = {y: 6};
var instance2 = new obj();
you are changing the prototype
object of obj
, so that the new objects constructed with this functions will use the new object assigned to it. So, [[Prototype]]
looks like this, for instance2
console.log(Object.getPrototypeOf(instance2));
# { y: 6 }
That is why, instance2
couldn't find x
in it, but y
.
To answer the updated question,
EDIT: How would I go about changing the prototype of ALL the instances?
You can change, the prototype of the old object with Object.setPrototypeOf
, like this
Object.setPrototypeOf(instance1, {
y: 6
});
Since, this makes the [[Prototype]]
of instance1
different from instance2
, we can just update the constructor function's prototype
object, like this
delete obj.prototype.x;
obj.prototype.y = 6;
Now, we havn't changed the internal property of both instance1
and instance2
. We can check that like this
console.log(Object.getPrototypeOf(instance1) === Object.getPrototypeOf(instance2));
# true
console.log(Object.getPrototypeOf(instance1) === obj.prototype);
# true
Note: The convention is to name the constructor functions with the initial letter a capital letter.
Upvotes: 7
Reputation: 1500
Explanation
So first, your two lines of code create a function, obj
, and assign to it's prototype {x: 5}
.
When you create an instance of this object, it seems to have an internal reference to the prototype that existed when it was new
'd.
After this, you reassign the prototype to {y: 6}
which does not affect the instance1
internal reference to the first prototype.
Then when you create instance2
it has an internal reference to the 2nd prototype and therefore, logging them will produce 5, undefined, undefined, 6
.
#4
You could, rather than reassign the prototype to a new object:
obj.prototype = {y: 6};
Modify the prototype instead:
delete obj.prototype.x; // Setting to undefined should produce same behaviour
obj.prototype.y = 6;
This will produce the output: undefined, 6, undefined, 6
I have tested this with http://jsfiddle.net/9j3260gp/ on Chrome and Firefox latest versions on Windows.
Upvotes: 9
Reputation: 20928
The prototype of instances do not reference the class, instead they reference the prototype object itself. This will become clear when you try Object.getPrototypeOf()
to see which prototype object the instance references.
Object.getPrototypeOf(instance1)
Object { x: 5, 1 more… }
Object.getPrototypeOf(instance2)
Object { y: 6 }
This field getPrototypeOf
references is supposed to be an internal one which exists for each instance. Before getPrototypeOf
existed, you could get this via __proto__
.
Upvotes: 1
Reputation: 82267
prototype is a feature which is paired with new behind the scenes. It applies to all instances of that function used with new. In the first example, you append .x = 5 to the prototype, and the instance you create has .x =5 as a value. Later you modify the prototype to be a new object. Now this is the prototype which is used in any new instances. So this is why the first instance only has .x = 5, and the second only has .y = 6
Upvotes: 1