Marco Bonelli
Marco Bonelli

Reputation: 69276

What's the JavaScript's Object.prototype behavior?

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:

  1. Why is this logging 5, undefined, undefined, 6 instead of undefined, 6, undefined, 6?
  2. Why replacing the prototype is not changing the prototype of all the instances of the object, as it would usually do?
  3. What is the V8 engine doing, step by step, in this code?
  4. EDIT: How would I go about changing the prototype of ALL the instances?

Every explanation is appreciated.

Upvotes: 12

Views: 1250

Answers (6)

T.J. Crowder
T.J. Crowder

Reputation: 1074028

  1. Why is this logging 5, undefined, undefined, 6 instead of undefined, 6, undefined, 6?
  2. 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:

  1. 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.

  1. 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

Kokozaurus
Kokozaurus

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

thefourtheye
thefourtheye

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

Winestone
Winestone

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

simonzack
simonzack

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

Travis J
Travis J

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

Related Questions