Careful with that prototype, Eugene

Here's one that could cost you some long and puzzling debugging sessions.

What's the difference between

Foo.Bar = function() {
  this.number = 0;
  this.stuff = {};
}
Foo.Bar.prototype.toString = function() {
  return "Bar #" + this.number + (this.stuff.id || "");
}
and
Foo.Bar = function() {}
Foo.Bar.prototype.number = 0;
Foo.Bar.prototype.stuff = {};
Foo.Bar.prototype.toString = function() {
  return "Bar #" + this.number + (this.stuff.id || "");
}
Well, it becomes obvious when you try the following:
var a = new Foo.Bar();
var b = new Foo.Bar();
a.number = 1;
a.stuff.id = "a";
b.number = 2;
b.stuff.id = "b";
alert(a.toString());
alert(b.toString());

In the first case, you'll get "Bar #1 a" and "Bar #2 b". In the second "Bar #1 b" and "Bar #2 b". The reason is that both instances of Foo.Bar have the same prototype, so they both share the same object as their "stuff" field. If you set it on any instance, it affects all of them. In the first case, a new object is created from each instance's constructor.

Using the prototype to keep the cost of constructing instances as low as possible is a good idea in general but as most powerful concepts it must be handled with care. In the next article, I'll detail the difference between the closure style of JavaScript type definition and the prototype style. This is one of the caveats to keep in mind when going the prototype way.

Now, this could very well be taken advantage of by defining a "static" blob on the prototype to simulate the concept of fields shared between instances. That would kinda work but it would be a little weird to have statics hanging off a field on instances and it would be easy to overwrite this static blob off any instance. But the best argument against that is that defining statics as members of types (i.e. constructor functions) works much better and is more natural. The only problem with the latter method is statics are hard to differentiate from instance members of the type Function.

9 Comments

  • I've run into this on a few occasions, and it's one of those things that are totally non-obvious about JavaScript - until you hit it...

    I've come to think of prototype like a sort of static interface with closures being more like a typical instance interface.

    It depends on the situation, but it seems to me one should be really careful with assigning protypes because you may never know how a class is used.

  • Manuel, you'll see in the next CTP that all Atlas classes are now prototype-based.
    I'll fix the post.

  • Bertrand,

    that's good news! Is the next CTP comming out soon? :)

  • Hi,

    I still remember that headache... I've read about this tip only in a book entirely dedicated to JavaScript. Thanks for sharing it.

  • Manuel: we're working really hard on the new CTP currently. I can't tell you when it will be ready exactly yet, but we're doing everthing we can to get that into your hands as soon as possible.
    I'll make another post soon about the transition from closure to prototype, its motivations and caveats.

  • Bertrand: Thanks for the information. As the last release was a few months ago I'm a bit impatient to get the latests bits in my hands. The post you talk about sounds really interesting...

  • Craig. Read on to the next article: http://weblogs.asp.net/bleroy/archive/2006/10/11/From-closures-to-prototypes_2C00_-part-1.aspx
    :)

  • Bertand -

    Thanks for your informative posts.

    I tired out he example and came up with a combination of the two patterns shownm above:


    Foo.Bar = function() {
    this.stuff = {}
    }
    Foo.Bar.prototype = {
    number: 0,
    toString: function() {
    return "Bar #" + this.number + (this.stuff.id || "");
    }
    }



    Any reason why someone shouldn't code it this way?

  • Patrick: sure, that's what we're doing in ASP.NET AJAX, see my more recent posts on prototype. In this post, I tried to keep it simple and without dependancy on AJAX.
    The problem with your code if you add nothing else is that you broke the prototype's constructor by overwriting the whole prototype with a JSON object. We solve this in the AJAX Library by resetting prototype.constructor to Foo.Bar from the class registration method.

Comments have been disabled for this content.