Two gotchas every CoffeeScript and Backbone.js developer should know

Sage Griffin

If you’re a Ruby developer working with Rails, at some point you’re going to need to work with JavaScript. While the two languages have many similarities, the fundamental differences in their object models can be quite jarring. CoffeeScript helps to provide a more Ruby-like syntax, but if you’re not careful you can introduce bugs to your code in surprising ways.

Let’s look at an example. Let’s say we have an expensive operation, and need to cache the result. In CoffeeScript, we might write the this code as:

class GiantRobot
  smashCache: {}

  smashInto: (other) =>
    @smashCache[other] ||= @expensiveCalculations(other)

However, this code leads to a surprising problem when we start smashing things together.

ralph = new GiantRobot()
voltron = new GiantRobot()

ralph.smashInto(optimusPrime)   # => not cached
ralph.smashInto(optimusPrime)   # => cached
voltron.smashInto(optimusPrime) # => cached?!

To see why this occurs, we need to take a look at how objects work in JavaScript. Unlike Ruby, JavaScript has no concept of classes. Instead it constructs its objects using prototypes. If you’re writing your code in CoffeeScript, you can almost always ignore this fact, and write your code as if you were in Ruby or Python. In this case, however, CoffeeScript is making our problem less apparent, so let’s take a look at what this code looks like using plain JavaScript.

function GiantRobot() {}

GiantRobot.prototype.smashCache = {};

GiantRobot.prototype.smashInto = function(other) {
  if (!this.smashCache[other]) {
    this.smashCache[other] = this.expensiveCalculations(other);
  }
  return this.smashCache[other];
};

Specifically, our problem stems from the fact that setting smashCache on the prototype may not work the way you’d think.

How prototypes work

When we create ralph with, ralph technically has no smashCache property. When we do this.smashCache, JavaScript looks for that property on ralph, followed by ralph‘s prototype, then ralph’s prototype’s prototype, and so forth until it finds something that has the smashCache property.

JavaScript provides us a way to see this in action, using the hasOwnProperty method.

ralph.hasOwnProperty('smashCache')                # => false
GiantRobot.prototype.hasOwnProperty('smashCache') # => true

So when we call this.smashCache, we are always getting back GiantRobot.prototype.smashCache, which means when we mutate it, we are mutating the same instance of smashCache that is used by every instance of GiantRobot. This is only an issue when we’re talking about arrays and objects.

ralph.smashCache = 'some value'
# Variable assignment sets the property directly on the instance
ralph.hasOwnProperty('smashCache') == true

Avoiding mutating the prototype

So how do we get around this? Ironically, the answer is simply to make our original example look more like our Ruby code.

class GiantRobot
  constructor: ->
    @smashCache = {}

Now each instance will have it’s own separate smashCache, and all is well with the world.

Backbone.js gotcha: Mutating the defaults object

A similar problem exists in Backbone.js when using the defaults object on your models.

class RobotProfile extends Backbone.Model
  defaults:
    images: []

  addImage: (newImage) ->
    @get('images').push(newImage) # => Mutates a shared instance

The solution for this problem is even simpler. If we change the defaults property to be a function that returns the same hash, Backbone will call it every time a new instance is created.

class RobotProfile extends Backbone.Model
  defaults: ->
    images: []

Now our images array will no longer be shared across multiple instances, and we can mutate to our heart’s content.