CoffeeScript function binding gotcha when using cloned Spine models

1 minute read

I’ve come across a CoffeeScript “gotcha” whilst cloning Spine models. Let’s say we have model as such:

Spine = require("spine")

class MyModel extends Spine.Model
  id : 1
  output: () =>
    console.log @id

The output() method is always executed within the context of the instance of MyModel on which it gets called. Now let’s see what happens when we clone it:

foo = new MyModel()
foo.id = 2
foo.output() # outputs: 2

bar = m.clone()
bar.id = 3
bar.output() # outputs: 2

We expect bar.output() to return 3 yet it returns 2. Why is this? It’s because the output() method is bound to the instance (this) within MyModel’s constructor, as can be seen from the generated Javascript:

(function() {
    var MyModel, Spine, cp, m;
    var __bind = function(fn, me) {
            return function() {
                return fn.apply(me, arguments);
            };
        },
        __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;
        };
    Spine = require("spine");
    MyModel = (function() {
        __extends(MyModel, Spine.Model);

        function MyModel() {
            this.output = __bind(this.output, this);
            MyModel.__super__.constructor.apply(this, arguments);
        }
        MyModel.prototype.id = 1;
        MyModel.prototype.output = function() {
            return console.log(this.id);
        };
        return MyModel;
    })();
}).call(this);

Thus, even when calling output() on a cloned object its function context will still point to the original instance it was cloned from. The solution - if you’re going to need to clone your model instance - is to not use CoffeeScript’s function binding. Using the non-binding syntax we get the results we want:

Spine = require("spine")

class MyModel extends Spine.Model
  id : 1
  output: () ->
    console.log @id

foo = new MyModel()
foo.id = 2
foo.output() # outputs: 2

bar = m.clone()
bar.id = 3
bar.output() # outputs: 3

Leave a Comment