2011-06-23

Translating CoffeeScript classes to JavaScript

The post “Classes in Coffeescript” contains an interesting juxtaposition of CoffeeScript code and the JavaScript it is translated to. This post examines the result of the translation in more detail, which nicely illustrates how subclassing works in JavaScript. To understand the following, you should be familiar with JavaScript’s prototypal inheritance (explained here).

The translation

Given the following CoffeeScript code of a superclass Ninja and a subclass Ronin.
    class Ninja
        constructor: (@numOfShurikens) ->
  
        throwShuriken: ->
            @numOfShurikens--

    class Ronin extends Ninja
        constructor: (numOfShurikens) ->
            super numOfShurikens+1 # ronins know to carry a spare
Subclassing in JavaScript is a bit tedious and CoffeeScript helps. The above code translates to the following JavaScript.
    var Ninja, Ronin;
    var __hasProp = Object.prototype.hasOwnProperty,
        __extends = function(child, parent) {
            for (var key in parent) {
                if (__hasProp.call(parent, key)) {
                    child[key] = parent[key];
                }
            }
            function ctor() { this.constructor = child; }
            ctor.prototype = parent.prototype;
            child.prototype = new ctor;
            child.__super__ = parent.prototype;
            return child;
        };

    Ninja = (function() {
        function Ninja(numOfShurikens) {
            this.numOfShurikens = numOfShurikens;
        }
        Ninja.prototype.throwShuriken = function() {
            return this.numOfShurikens--;
        };
        return Ninja;
    })();

    Ronin = (function() {
        __extends(Ronin, Ninja);
        function Ronin(numOfShurikens) {
            Ronin.__super__.constructor.call(this, numOfShurikens + 1);
        }
        return Ronin;
    })();

Understanding the generated code

Let us break down the code and explain each piece:
    var Ninja, Ronin;
Declaring the variables now and assigning them later makes hoisting [3] explicit. I don’t see an advantage to declaring the two variables later, when each of the assignments is made.
    var __hasProp = Object.prototype.hasOwnProperty,
Shortcut for hasOwnProperty, slightly increases performance, because dereferencing happens less often. On the other hand, it feels unnecessary to waste a global variable for this, putting it directly in front of the for loop would probably have been enough.
        __extends = function(child, parent) {
            
            // (1)
            for (var key in parent) {
                if (__hasProp.call(parent, key)) {
                    child[key] = parent[key];
                }
            }
            
            // (2)
            function ctor() { this.constructor = child; }
            ctor.prototype = parent.prototype;
            child.prototype = new ctor;
            
            // (3)
            child.__super__ = parent.prototype;
            
            return child;
        };
Remember that a constructor function C loosely corresponds to a class in that it carries the name of the type and that it is used to produce new instances. However, the instance methods (which are shared by all instances) are put into C.prototype. When you subclass, you thus have to consider two levels: On one hand class methods C.classMethod(), on the other hand instance methods C.prototype.instanceMethod(). Beware that the prototype property of C only has hat name because it will become the prototype of objects that are created via new C(). Thus, C.prototype is not the prototype of the function C, but rather a property of the function object. Steps performed above:
  1. Copy class methods from the superclass to the subclass. There is no other way to inherit such methods in JavaScript.
  2. Ensure that the object child.prototype has the prototype parent.prototype. You do this by creating a temporary constructor ctor and giving it the right value for ctor.prototype. The code fragment is equivalent to the following ECMAScript 5 [1] code:
        child.prototype = Object.create(parent.prototype);
        child.prototype.constructor = child;
    
    The constructor property [2] is correctly set up in the default prototype. As we assign our own prototype object, we need to set up that property manually.
  3. Given a superclass Super and a subclass Sub, you normally have to refer to Super by name in the methods of Sub. By assigning Super.prototype to Sub.__super__, you avoid this kind of hardcoding. The Ronin() constructor function uses Ronin.__super__ to that effect.
    Ninja = (function() {
        function Ninja(numOfShurikens) {
            this.numOfShurikens = numOfShurikens;
        }
        Ninja.prototype.throwShuriken = function() {
            return this.numOfShurikens--;
        };
        return Ninja;
    })();
A normal JavaScript class definition, wrapped in an IIFE [3]. There is no benefit for using an IIFE here, apart from having a single assignment (which would matter in an object literal, but doesn’t here). Note that the identifier Ninja in the first line is a different variable than the identifier Ninja inside the IIFE (subsequent lines).
    Ronin = (function() {
        __extends(Ronin, Ninja);
        function Ronin(numOfShurikens) {
            Ronin.__super__.constructor.call(   // (*)
                this, numOfShurikens + 1);
        }
        return Ronin;
    })();
Again, the IIFE is not needed. The __extends() function that was defined previously is used to perform all of the necessary wiring from subclass to superclass. Caveat: the first argument refers to function Ronin() below, because that function is hoisted [3] (roughly: moved to the top of the surrounding function). It would have been better to invoke __extends() after the declaration of Ronin(), to make that fact explicit. Furthermore at (*), Ronin refers to a binding inside the IIFE environment which keeps that environment alive after leaving the IIFE.

The invocation of the super-constructor is a typical JavaScript technique: You invoke the super-constructor as a function (no new!) and give it the current this, so that it adds its properties, but does not create a new instance.

Related reading

  1. What’s new in ECMAScript 5
  2. What’s up with the “constructor” property in JavaScript?
  3. JavaScript variable scoping and its pitfalls [explains IIFEs and hoisting]
  4. Lightweight JavaScript inheritance APIs [various ways of simplifying JavaScript inheritance]
  5. CoffeeScript versus paren-free JavaScript
  6. ECMAScript.next: prototypes as classes [a proposal to simplify inheritance for the next version of JavaScript]

No comments: