(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