Curly braces are not the problem, vol. II

April 14, 2010

A few months ago I wrote a post about JavaScript titled Curly braces are not the problem wherein I pointed out one of JavaScript’s biggest weakness, the new operator and how to spell an object constructor as well as methods on the corresponding prototype. Some commentators mistook that post for critique of the prototype model itself. It was far from it, I think the prototype model is great, just the spelling was awful. Consider this:

function MyObject() {
    /* constructor here */
}
MyObject.prototype = {
    aMethod: function () {
        /* method here */
    }
};

which is alright until you now want to inherit from this and add methods:

function YourObject() {
    /* constructor here */
}
YourObject.prototype = new MyObject();
YourObject.prototype.anotherMethod = function () {
    /* another method here */
};

There are several problems with this. First of all because YourObject inherits from MyObject, it has to be spelled differently. Secondly, we can’t reuse the constructor, at least not without resorting to func.apply() tricks. Thirdly, we have to know what to pass to the constructor of MyObject at definition time.

It turns out, Doug Crockford not only agrees with me on this but also has come up with a better way. Back in January I thought that we needed more syntax to fix this, but it turns out we need less (by which I mean ditching the new statement). In Vol. III of his excellent Crockford on JavaScript lectures, he defines a constructor maker:

function new_constructor (extend, initializer, methods) {
    var prototype = Object.create(extend && extend.prototype);

    if (methods) {
        methods.keys().forEach(function (key) {
            prototype[key] = methods[key];
        });
    }

    var func = function () {
        var that = Object.create(prototype);
        if (typeof initializer === 'function') {
            initializer.apply(that, arguments);
        }
        return that;
    };

    func.prototype = prototype;
    prototype.constructor = func;
    return func;
}

I’ll let you work out the details of this yourself and instead just show you how you would define the equivalent of the two cases above:

var new_my_object = new_constructor(Object, function () {
    /* constructor here */
}, {
    aMethod: function () {
        /* method here */
    }
});

var new_your_object = new_constructor(my_object, function () {
    /* constructor here */
}, {
    anotherMethod: function () {
        /* method here */
    }
})

See how symmetrical both forms are now? And if both object constructors really were were to share the same initializer, I could easily define that as a separate function and reuse it.

Btw, if you do any sort of web development, I highly recommend you watch the Crockford on JavaScript talks. They’re not only entertaining but are an excellent lesson in history of all the technology that makes up the web.

6 Responses to “Curly braces are not the problem, vol. II”

  1. philikon Says:

    Btw, new_constructor relies on the Object.keys() method which is only available in ES5. It’s easy to patch it in, though:

    Object.prototype.keys = function () {
        var keys = [];
        for (var key in this) {
            keys.push(key);
        }
        return keys;
    };
  2. Ian Bicking Says:

    Will that keys patch be right? Or will it add “keys” to every call to {}.keys()? (Well, I’m not sure how keys is defined.)

    • philikon Says:

      Yup, the way I’ve defined it above will mean that ‘keys’ will always be part of the keys:

      js> ({}).keys()
      ["keys"]

      The only way to fix this is to declare the ‘keys’ property non-enumerable. But this will again require ES5 (though it already works in Chrome and Firefox 3.7):

      Object.defineProperty(Object.prototype, 'keys', {
          writeable: true,
          enumerable: false,
          configurable: true,
          value: function () {
              var keys = [];
              for (var key in this) {
                  keys.push(key);
              }
              return keys;
          }
      });
  3. philikon Says:

    I also forgot to mention Object.create which new_constructor relies upon as well. It’s a very nice and easy way of creating new objects that inherit from another one. It’s not available in most browsers (except Chrome and Firefox 3.7) so you again have to patch it in:

    if (typeof Object.create !== 'function') {
        Object.create = function (obj) {
            function Func() {}
            Func.prototype = obj;
            return new Func();
        };
    }

    (The typeof ... !== 'function' check should also be applied to the Object.keys patch.)

  4. Balazs Ree Says:

    The most sane solution I found is of Dean Edwards:

    http://dean.edwards.name/weblog/2006/03/base/

    This gives inheritance as we use it in python, the above page contains all examples of usage. The supporting code is also very small.

    Unfortunately, this or a similar solution did not make it as a standard part of jquery.

    • philikon Says:

      That’s because I think it’s not necessary. Sure, you *can* impose something like a class system onto your JavaScript objects, but why? Prototypes work just as well, *if* you don’t fall into the semiclassical trap of the new operator.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: