Backbone.js: Hacker's Guide Part 2

26 Jul 2012 | By Alex Young | Tags mvc tutorials backbone.js code-review

Last week we looked at Backbone.js’s internals, covering configuration, server support, events, and models. I actually really enjoy looking at projects this way, it’s one of the best ways to learn new programming techniques. So let’s continue dissecting Backbone by taking a look at Backbone.Collection.

Constructor

Backbone.Collection is a constructor function that accepts an array of models and an options object.

As an aside, notice that void 0 is used in this code. To understand why, recall that the void operator returns undefined. Since ECMAScript 5, the undefined property isn’t writable, so it’s safe to use it. However, in earlier versions it was writable, which meant malicious code could technically take advantage of this fact by assigning a value to the undefined property of the global object. The void operator expects an expression, so void 0 is considered the idiomatic way of safely obtaining undefined.

The constructor calls the reset method, which removes existing models and adds new ones. This is similar to instantiating a collection with no models, and then manually calling add on each one.

Inheritance and Mixins

The Collection class inherits from Backbone.Events. Events are used both publicly and internally. There’s a toJSON method that iterates over each model and calls the model’s toJSON method. This brings up an interesting point: collections use methods from Underscore.js, but Collection doesn’t inherit from Underscore. Why not? Well, certain methods are manually assigned to Collection.prototype, while others are rewritten in ways that make sense in Backbone. For example, the pluck method works on model attributes, and sort uses the boundComparator which has a slightly different API to Array.prototype.sort.

Adding and Removing Items

Collections are basically an array of models with events, wrapped with convenient Underscore-like iterator methods. The add method is always called however models are added, which means it’s a good place to do housekeeping like preventing invalid models and duplicates from being inserted into the collection. Models are also indexed by id, and all model events are bound to _onModelEvent. This method dynamically adds new models, removes deleted ones, and updates models with changes.

If the collection requires sorting, the add method will call sort once all models have been processed. And, if the silent option isn’t set, an add event will be triggered for each model that was successfully added.

It naturally follows that the remove method has a fair amount of work to do, given the complexity of add. The indexed ids must be deleted, and _removeReference is called to remove the model’s reference back to the collection.

Deleting items in JavaScript is interesting, because we actually have the delete keyword to do this for us. However, delete is only used for properties, so the authors have used the Array.prototype.splice technique to delete models from the array. The add and remove methods also update the length property, which allows the collection to behave in an Array-like manner, and helps support the mixed-in Underscore methods.

Now take a look at the simplicity of the where method. It basically loops over each model, comparing an attributes object. This is simple because the filter method is taken directly from Underscore.

Chainable API

Another bit of sugar is the support for Underscore’s chain method. This works by calling the original method with the current array of models and returning the result. In case you haven’t seen it before, the chainable API looks like this:

var collection = new Backbone.Collection([
  { name: 'Tim', age: 5 },
  { name: 'Ida', age: 26 },
  { name: 'Rob', age: 55 }
]);

collection.chain()
  .filter(function(item) { return item.get('age') > 10; })
  .map(function(item) { return item.get('name'); })
  .value();

// Will return ['Ida', 'Rob']

Some of the Backbone-specific method will return this, which means they can be chained as well:

var collection = new Backbone.Collection();

collection
    .add({ name: 'John', age: 23 })
    .add({ name: 'Harry', age: 33 })
    .add({ name: 'Steve', age: 41 });

collection.pluck('name');
// ['John', 'Harry', 'Steve']

Conclusion

I’ve been using Backbone for a while, and I’ve never really thought about how the Backbone.Collection methods can be chained. Sometimes it’s difficult to tell what’s possible though – once you’re in an Underscore chain you can’t use methods like pluck because Backbone’s models use the get method to access attributes, so you’ll end up with an array of undefined values.

Next week I’ll continue looking at Backbone by investing the formidable routing and history APIs.


blog comments powered by Disqus