3

So I would like to say I understand JavaScript fairly well, but it seems that the language always likes to throw a wrench at me. I have a problem (well maybe not problem, but for sure confusion) on the behavior of "instance" variables for JS objects. Lets take a look at the following code

function A(a) {
    this.a = a;  
    this.b = [];
}
function B(a) {
   //A.apply(this, arguments);
    this.a = a
}
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.append = function(b) {
    this.b.push(b);  
};

var one = new B("one");
var two = new B("two");
console.log(one.a);
console.log(two.a);
one.append(10);
console.log(one.b);
console.log(two.b);

If you run this code, you will see that one.b and two.b have shared the same b "instance" variable array. I have only noticed this happening on arrays. I understand that if this was A.prototype.b = [], that it would definitely be shared across all instances of A or its children. What I don't understand is that it is defined with 'this' command, which means it should be per instance. The interesting thing is that if you uncomment the A.apply(this, arguments) (effectively calling super on the parent) and remove this.a = a inside the B constructor, it works as you think it would. Would anyone know the reason why this is happening?

1
  • Mutating shared members that are on the prototype. Explained here: stackoverflow.com/questions/16063394/… you should not create an instance of parent to set the prototype part of inheritance. You'll have instance specific values of parent on child prototype (shared by child instances) even if you un comment the apply and shadow these members they are still there on the prototype Commented Jun 18, 2014 at 0:34

2 Answers 2

3

You are doing B.prototype = new A(); which is equivalent to

B.prototype.a = undefined;
B.prototype.b = [];
// or actually
// B.prototype = {a: undefined, b: []};

That's where the shared array comes from.

You are not calling the A constructor for each instance of B. Uncomment the A.apply(this, arguments); line and it will work as expected.

See Benefits of using `Object.create` for inheritance for a better way to setup inheritance.

Sign up to request clarification or add additional context in comments.

4 Comments

If that is the case, then why doesn't one.a and two.a both equal to "two"? Lets look at this: function A() { this.a = "hi"; this.b = []; } function B() { } B.prototype = new A(); B.prototype.constructor = B; B.prototype.append = function(b) { this.b.push(b); }; B.prototype.name = function(a) { this.a = a; }; var one = new B(); var two = new B(); one.name("one"); two.name("two"); console.log(one.a); console.log(two.a); one.append(10); two.append(20); console.log(one.b); console.log(two.b);
dude, that one made my eyes bleed.
@zero: because you are assigning to this.a which always creates (or updates) property on the object itself (whether it exists on the prototype or not). this.b.push(...) is not assigning, it's reading this.b and mutates the result (the array). In this process, b is looked up in the prototype chain.
Ah ok, that makes sense then. So either make sure to do A.apply, or don't define the array in the super class, but in the subclass. Thanks
1

Part of the problem here has to do with the prototype property of Functions, versus the [[Prototype]] hidden property of objects. I'm not sure if any implementations actually use that name for the hidden property -they don't have to, because it's hidden- but some implementations use the non-standard name __proto__ to make it accessible to the script code. I'm going to use this name, even though it's non-standard, to make the distinction clearer between the two; you'll see why in a moment.

The new operator does a couple of things here. First, it creates a new object and sets its __proto__ to the prototype property of the function that it's about to call. Then it calls that function, with the new object set to this.

In other words, you didn't define this.b with the this keyword. You defined it all the way back when you created B.prototype. The result of creating your object one looks something like this:

{
    "a" : "one",
    "__proto__" : {
        // This is a reference to B.prototype, which was created with your call to new A()
        "a" : undefined /* because your call to new A() didn't have any arguments */,
        "b" : [],
        "constructor" : /* reference to B() */,
        "append" : /* reference to the append() function you defined */,
        "__proto__" : undefined /* this would reference A.prototype if you had defined one */
}

See the extra a and b on your __proto__? Those were created when you called new A() to define B.prototype. The a isn't likely to do much of anything, because it's behind another a in the prototype chain, so things seem to work well enough (most of the time). But that b is another matter, because it isn't behind anything.

When your call to B.prototype.append references this.b, it first looks at the root level of the object for a b property, and it doesn't find one (because you never defined it). But your object has a __proto__, so it looks there for a b: this is called the prototype chain. And it finds one, so it appends to that array. Because all the objects you create with new B() share a reference to that same object, this gets shared among all the different members.

Let's swap the comments around the way you mentioned. What does the object look like now?

{
    "a" : "one",
    "b" : [],
    "__proto__" : {
        // This is a reference to B.prototype, which was created with your call to new A()
        "a" : undefined /* because your call to new A() didn't have any arguments */,
        "b" : [],
        "constructor" : /* reference to B() */,
        "append" : /* reference to the append() function you defined */,
        "__proto__" : undefined /* this would reference A.prototype if you had defined one */
}

Do you see the difference? Because A() actually got a chance to run on this during the call to new B(), it put a b at the root of the object, and this one belongs to only that object: it's not part of any __proto__ or anything. You've still got that shared b hanging back in the prototype chain, but it's got another b in front of it now, so B.prototype.append() will never see it. It'll find the instance-specific b first, and that's why your second example works the way you intended.

One last point. Those shared a and b properties back in the prototype chain aren't really doing you any good: in this particular code, they don't do anything but take up space. It IS possible for code to make use of them, but that doesn't suit your use case, so it would be best to get rid of them.

How do you do that? You have to do it without actually calling A(), because that would set the a and b properties back in the prototype chain, and you don't want that, but this means you can't use new A(). But there's another way to create objects that does what you want: the Object.create function. You pass it the object that you want to become the __proto__ of the new object, so it looks like this:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

I usually wrap this rigamarole up in a function, like so:

Object.subclass = function (sub, sup) {
    sub.prototype = Object.create(sup.prototype);
    sub.prototype.constructor = sub;
}

Once I've got that, I can just call Object.subclass(B, A), which is a pretty clear representation of what I want. It does mean that I have to call A() in the constructor for B(), just like you describe, but the resulting object doesn't have the extra junk from calling new A() earlier in the process.

1 Comment

Super awesome explanation. It helps better explain what the first comment was getting to.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.