From closures to prototypes, part 1

There are basically two ways to define classes in JavaScript: closures, where the constructor of the class both adds methods to the new instance and provides a common context for them, and prototypes, which are the first place JavaScript will look at when looking for a member that isn't directly defined on the instance. Don't worry if you don't understand everything I just said, I'll show and explain.

Closures are functions that are defined in a context. This context is the sum of all the objects that are defined at the scope where the function is defined. The catch is that the function not only can access this context, it will also take it with itself everywhere it goes. In other words, closures are functions that remember where they came from. In JavaScript, functions automatically get that quality. Here's an example:

function createClosure(a) {
  var b = 42;
  return function(c) {
    return "a=" + a + ", b=" + b + ", c=" + c;
  }
}

var closure1 = createClosure(1);
alert(closure1(8)); // a=1, b=42, c=8
alert(closure1(9)); // a=1, b=42, c=9

var closure2 = createClosure(2);
alert(closure2(10)); // a=2, b=42, c=10
Coupled with the semantics of the this pointer, closures enable the construction of class instances that have real private variables:
function Point(x, y) {
  var _x = x;
  var _y = y;

  this.get_x() {
    return _x;
  }

  this.get_y() {
    return _y;
  }
}

In this example, we have constructed a truly immutable Point structure. There is no way to modify the private members _x and _y, and you can only get their values through the accessors. It's nice to have truly private members and it's actually useful to build security in mashup scenarios where pieces of code come from untrusted sources. It's extremely tricky to get right but it can be done.

Now in most other situations, it's not really of paramount importance that your private variables remain untouchable. Actually, in .NET, private members are accessible through reflection. You need reflection permissions, but they sure are accessible. Furthermore, there is one circumstance where you do want your private variables very accessible, and that is when debugging. Inspecting private fields is one of the main things you want to be able to do from a debugger, especially when property accessors are not being automatically evaluated by the tool.

So even if it seemed like a good idea to get really private variables, well it really isn't (except when you really, really need them).

But there's another problem with closure-based classes and that's performance. For every instance you create, all the methods have to be recreated. This means that you pay a higher price to object creation and that the working set gets bigger with each instance.

Let's look at prototype-based classes and see the pros and cons of that approach. Here's the same point structure built using prototypes:

function Point(x, y) {
  this._x = x;
  this._y = y;
}
Point.prototype.get_x = function() {
  return this._x;
}
Point.prototype.get_y = function() {
  return this._y;
}

In this case, _x and _y are only private by convention. Nothing prevents anyone from tampering with them. In most cases though, that's fine, and you actually want that to be possible from the debugger.

Performance-wise, prototypes are also better in almost every situation. First, they minimize the working set: here, there's only one copy of the get_x and get_y functions for all Point instances. What differentiates the instances is only what this points to. In terms of speed, creating instances is faster, but calling methods is just a little slower because of the need to lookup the method on the prototype chain.

So far, Atlas has been using closures. Starting with the next version, all of our classes will be using prototypes. Closures are still supported but prototypes are the recommended pattern.

In part 2, I'll show how to easily transition a closure-based Atlas class library to prototypes, with clear do's and don't's.

Read on: http://weblogs.asp.net/bleroy/archive/2006/10/14/From-closures-to-prototypes_2C00_-part-2.aspx

10 Comments

  • This is a great article. I suppose in the next installment you will cover how prototypes can be dangerous when expecting "virtual" function behavior and the fact that prototypes are static instances that, once defined, change the behavior of all versions of the function called after a new prototype has been defined?

    Because the Atlas closure model with the "getTypeDescriptor()" semantic allows for overriding and calling "base class implementations" it might be confusing to make the right decision about what model to use.


  • what about the following? same thing, just seems easier to read/associate the grouping.

    function Point(x, y){
    this._x = x;
    this._y = y;
    }
    Point.prototype = {
    get_x: function() {
    return this._x;
    },
    get_y: function() {
    return this._y;
    }
    }
    var x = new Point(2,5);
    alert(x.get_x());


  • Awesome. Thansk Bertrand.

  • Michael: yes, absolutely, this will be covered in part 2, and it is the style we're using in our own classes but I wanted to stay out of any Atlas-isms in the first part. There's something missing in your example though, as if you set the prototype this way, you also need to (re)set the constructor. If using Atlas, this is taken care of when registering the class. More on that later.

  • Ludovico: this is *sample code* that has no other goal than to illustrate the discussion.

    If I had to write a point in "real life", I would do var point = {x: x, y: y};

    Thank you very much for explaining JavaScript to me but you have to think in the context of a larger and more useful class and transpose what I'm saying to that context.

  • Is that so? How do you explain "new", "this", "instanceof" or "constructor"? What about sections 13.2 and 15 of the EcmaScript specs? How come JavaScript 2.0 has "class" and "namespace"? Are these also misguided attempts from beginners in the field to make JavaScript what it's not?
    Your position is highly dogmatic and difficult to hold.
    Furthermore, quite a lot of people still to this day consider that OOP is the best way to build reusable code and applications above a certain size. There is a demand today for frameworks that enable the application of reliable engineering methods to the browser. Our users, as well as the users of Dojo or other frameworks don't seem to consider modern JavaScript frameworks to be "broken since [their] very design". At least it seems to solve a real-world problem.

  • Except from citing "JavaScript gurus", what good arguments do you have? Saying that EcmaScript is broken is arrogant at best if you don't elaborate. What about JavaScript 2.0? Is it broken too? So are all these people highly incompetent and only you and Mr. Crockford have understood the True Nature of JavaScript?
    How does the Atlas type system prevent you from doing aggregation? I've even published a blog post on the subject a while ago with some sample code, and although I quite like the whole idea, most of the feedback I got was that it was only marginally useful.
    I'm really sorry that you don't share our views on client-side development but we're going to continue in this direction anyway as most of the feedback we get encourage us to do so.

  • Hey man, with all respect, you keep pushing up the "great" work in progress, and we might discuss that in depth, yet my original observation was of a more basic nature: JavaScript is an object-based dynamic language, while you are approaching it as a standard OO language, and building frameworks over this misconception.

    I might be wrong, but I can't be clearer than I am.

    Best luck to the team, and also to the unfortunate that happens to build on this stuff.

    Bye. -LV

  • Now, you know, that is as well feedback from the community. ;) -LV

  • Yes, that is feedback, but you are so far the *only one* to give us that kind of feedback. You seem to be confusing object oriented and class-based. JavaScript is object-oriented, but not class-based in the current version. We recognize and use the prototype and dynamic nature of JavaScript, but at the same time, we agree with experts such as Brendan Eich that there is also a need for classes and we're answering that need while waiting for JavaScript 2 to be available on all browsers. For example, representing widgets and behaviors with class instances is extremely useful and opens up lots of possibilities.

Comments have been disabled for this content.