3

So I have the following:

function A () { this.a = 0; }
function B () { this.b = 0; }
function C () {}

C.prototype = new B();

var c1 = new C();
var c2 = new C();

c1.b = 10;

console.log(c1.b); // 10 
console.log(c2.b); // 0

http://jsfiddle.net/Mars6/2/

When I change B.b to an object, it seems to store the same object for each new C:

function A () { this.a = 0; }
function B () { this.b = new A(); }
function C () {}

C.prototype = new B();

var c1 = new C();
var c2 = new C();

c1.b.a = 10;

console.log(c1.b.a); // 10 
console.log(c2.b.a); // 10 - I want this to be 0

http://jsfiddle.net/Mars6/1/

Can anyone explain what is going on/what the problem is.

Cheetah
  • 13,785
  • 31
  • 106
  • 190
  • Here is nice article that I think will explain the issue http://flippinawesome.org/2013/06/03/javascript-inheritance-how-to-shoot-yourself-in-the-foot-with-prototypes/ – jcubic Jun 10 '13 at 07:25
  • Maybe this is a better example to understand what happens: [http://jsfiddle.net/rdCgn/1/](http://jsfiddle.net/rdCgn/1/) – Alex Filipovici Jun 10 '13 at 07:41

2 Answers2

6

When you did

C.prototype = new B();

You didn't say "whenever you need a prototype, do new B". What you said is "assign the prototype to new B() (which shouldn't be very surprising). What you get is:

C.prototype.b.a === 0

Whenever you do new C, you're not duplicating C.prototype - you're just linking to it, to the same B objcet:

C.prototype = new B()
   ^    ^
   |    |
  c1    c2

c1.b === c2.b; //true

As you may know, objects can be changed to your heart's content. So when you do c1.b.a = 4, you're going through to the underlying object and messing around with it.

Edit: The first example works because of how the property resolution works. The b property does not reside on the objects c1 or c2. When you say "give me c1.b" the js engine does something like this:

  1. Is there a b property on c1? Nope, there isn't.
  2. Let's look at the prototype of c1 (the actual prototype, what the object got its methods and properties from - in this case, C.prototype)
  3. oh yes, it has a b property. Return it.

Which, in actual js, is this (spec):

function GetProperty (name, obj) {
    while (obj !== null) {
        if (obj.hasOwnProperty(name)) {
            return obj[name];
        }
        obj = Object.getPrototypeOf(obj);
    }
    return undefined;
}

So, in the case that b is an object, you've got a hold of an object. Changing it changes it like a normal object (do note that you're not assigning to c1.b directly - you'll see what I mean in a bit). Explained with arrows:

C.prototype.b = 0
   ^     ^
   |     |
  c1.b   c2.b

This is important, so I'll stress it again: When you get a hold on c1.b, you're getting an object, which is manipulated like any other object. Assigning properties to it is as any other normal object, and mutating it...well, mutates it.

Now, in the former case (c1.b = 10), you're actually assigning a property. This means you're creating a key/value pairing on the c1 object itself. So, in the first step, we look to see if c1 has a property b - and it has. Explained with more arrows:

      C.prototype.b = 0
            ^
            |
  c1.b=10  c2.b

Changing the latter example, we can observe the same effect:

//changing
c1.b.a = 10;
//to
c1.b = 4;

c2.b !== 4 && c2.b.a === 0; //true

To recap:

  • In the former example, you simply set a property on the c1 object.
  • In the latter example, you set a property on an object on c1s prototype, which mutated it on all other C objects.
Zirak
  • 38,920
  • 13
  • 81
  • 92
  • Does that explain the first case in my example, where I changed the underlying B and they were different? – Cheetah Jun 10 '13 at 07:31
  • @Ben In the first example, `C.prototype.b` wasn't an object, but a primitive value. When you accessed `b` in the latter example, you accessed it on `c1`s prototype link; in the former, you simply assigned it on the object. I'll edit my answer to explain this. – Zirak Jun 10 '13 at 07:33
  • In the first case you set a value to the instance that replaces the prototype value only for this instance. – atondelier Jun 10 '13 at 07:37
  • @Ben Edited. Hopefully it's clearer now. – Zirak Jun 10 '13 at 07:48
3

Here is whats happening as far as I know:

function A () { this.a = 0; }
function B () { this.b = 0; }
function C () {}

C.prototype = new B();

var c1 = new C();
var c2 = new C();

c1.b = 10;

When you write c1.b = 10, JavaSript will just add or change the local property b of the object c1. It doesn't care that the prototype already has a property b which in this case remains completely unaffected. Therefore:

console.log(c1.b); // 10  --> local property in c1
console.log(c2.b); // 0   --> prototype property

In your second example you are doing something different. Instead of assigning a property to c1, you are manipulating the prototype of it and thus affecting all instances together:

c1.b.a = 10;

c1 doesn't have a property b, so it will take it from the prototype object and add or change ITS property a. Now neither c1 nor c2 have gained a local property and therefore both referring to the changed prototype:

console.log(c1.b.a); // 10  --> points to the prototype
console.log(c2.b.a); // 10 --> points to the prototype

Possible Solution

c1.b = new A();
c1.b.a = 10
basilikum
  • 10,378
  • 5
  • 45
  • 58