CoffeeScript has a convenient way of emulating class and inheritance semantics via the class and extends keyword. I wanted to better understand what was happening under the covers so the following is an analysis of the transpiler output. We'll look at a simple relationship between two CS "classes":

class Parent
    constructor: ->
        @id = ++Parent.identity
    @identity: 0

class Child extends Parent
    url: 'http://www.google.com'

CS outputs the following (As of 1.2):

var Child, Parent;
var __hasProp = Object.prototype.hasOwnProperty, 
    __extends = function(child, parent) {/* discussed later */};

Parent = (function() {
    function Parent() {
        this.id = ++Parent.identity;
    }
    Parent.identity = 0;
    return Parent;
})();

Child = (function() {
    __extends(Child, Parent);
    function Child() {
        Child.__super__.constructor.apply(this, arguments);
    }
    Child.prototype.url = 'http://www.google.com';
    return Child;
)();

Some things I noted:

  • The CS transpiler builds the constructor functions in IIFE's so we don't clutter up the global object.
  • The CS "constructor" property is the body of the constructor function.
  • The CS @ prefix...
    • ... refers to the new object only when used in the constructor.
    • ... refers to the function object outside of the constructor (to declare "static" properties).
  • Properties not prefixed with the CS @ are prototype properties.
  • The "parent" constructor function can be executed in the context of the new child object. This happens automatically (As shown above) if the child does not explicitly declare a constructor. If the child object does supply a constructor (And you want to call the parent constructor) you will need to manually call super(...) in the child constructor with zero or more arguments.

Now lets take a look at the __extends method that is generated by CS. This method sets up the prototype chain and a property that links to the "parent" prototype.

var __hasProp = Object.prototype.hasOwnProperty;
var __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; 
};

Some things I noted:

  • All constructor function properties (Or "static" properties) are copied from the "parent" to the "child" constructor function.
  • An intermediate prototype object is created so that you can augment the "child's" prototype without changing the "parent's" prototype.
    • It's prototype is the "parent" prototype.
    • It serves as the prototype of "child" objects.
    • It's constructor is set to the "child" constructor function.
  • A special property called __super__ is created on the "child" constructor function. Evidentially CS likes to be backwards compatible as it doesn't make use of the relatively new Object.getPrototypeOf method. Calling super() in the CS constructor, as noted above, will make use of this property to call the "parent" constructor function.

From this we can see that CS sets up the prototype chain like so:

 

CoffeeScriptPrototypeChain

 

One more thing I was interested in was how the CS extends keyword compares to the underscore.js method of a similar name:

_.extend = function(obj) {
    each(slice.call(arguments, 1), function(source) {
        for (var prop in source) {
            if (source[prop] !== void 0) obj[prop] = source[prop];
        }
    });
    return obj;
};
Clearly they are very different. The underscore extend method basically does a mixin as it simply copies properties from the source objects to the target object.