Skip to content

Instantly share code, notes, and snippets.

@moschel
Last active August 23, 2016 13:03
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save moschel/5059553 to your computer and use it in GitHub Desktop.
Save moschel/5059553 to your computer and use it in GitHub Desktop.
CanJS vs Backbone

Backbone helps you structure your application. This is great, but it doesn't solve the whole problem. CanJS helps you structure your application and lets you actually focus on the part that matters, which is your data.

CanJS essentially gives you all the simplicity of Backbone (model and control layer and no weird asynchronous event loop like Ember and Angular), along with more advanced features that make writing apps much faster and the code more maintainable.

Backbone is popular because its very simple and easy to understand. They are aiming for that level JUST above basic jQuery development, people who just learned jQuery and are looking for a way to organize their code a bit more. It provides this. So does CanJS. But for large complex applications, Backbone lacks a lot of the things we need to make a great app.

Live Binding

Backbone has no concept of binding data object and their changes to your templates.

In Can, you only have to worry about how you represent the data once, while creating your view. In Backbone, every single time data changes, you have to either a) manually trigger an event that re-renders the entire app with the new data or b) write some "glue" code that specifically ties this change to a small part of the DOM:

'quantity:change' : function() {
  var newPrice = this.model.attributes.count * this.model.attributes.price;
  this.$el.find('div.results').html(newPrice);
}

Backbone provides a basic event system but fails to make this part of the view layer. This means that even when only one model attribute changes, large parts or all of the view template need to be re-rendered or the update needs to be handled manually.

Considering the following template:

<div id="cart">
  You have {{items.length}} items in your cart:
  <ul>
		{{#items}}
		<li>{{count}} {{name}} {{price}} <strong>{{total}}</strong></li>
		{{/items}}
	</ul>
	{{#if discount}}
	<p>Discount: <strong>{{discount}}</strong></p>
	{{/if}}
	Total: <strong>{{cart_total}}</strong>
</div>

In Backbone, whenever any of the the attributes changes (single item count, cart total etc.) the entire template needs to be rendered again or the specific attribute selected with jQuery and updated manually from the model information.

Both cases are less efficient than CanJS live-binding which only updates the attribute that actually changed automatically. After the template has been rendered for the first time, there is no need to take care of updating the view anymore. Working directly with the data lets you focus on what you actually want to do:

// Remove the last item. Removes the item and updates the cart total and item count.
this.cart.attr('items').pop();
// Update the count of the second item. Also updates the item and cart total automatically
this.cart.attr('items.1.count', 2);
// Add a discount, will show the discount section and update cart total
this.cart.attr('discount', 25.53);

Better Performance

Initializing controls performs better in CanJS than backbone: http://jsperf.com/tabs-timing-test/7

This is because when creating controls, Can caches several expensive calculations, so subsequent controls initialize very fast. This is important for complex apps with a lot of similar controls on the page.

Changes in templates appear much faster in CanJS than Backbone: http://jsperf.com/canjs-ejs-performance/5

This is because CanJS has true live binding and when one data attribute changes, it makes the smallest change possible to update that attribute's representation in the UI (ie changing a single element's attribute). Backbone must re-render the entire view for every change.

Application structure

Most client side MVC frameworks still encourage a folder structure similar to this:

- controllers/views
	- one.js
	- other.js
- models
	- subfolder
		- submodel1.js
	- model1.js
	- model2.js
- templates
	- one.handlebars
	- other.handlebars
- test
	- controllers
		- one_test.js
	- models
		-model1_test.js
- fixtures
	- subfolder
		- submodel1.json
	- model1.json

However, the DOM is a hierarchy and usually a JavaScript application is comprised of smaller parts that build on top of each other. CanJS (and steal) encourage an app structure where each "module" is a self contained folder, containing everything that modules needs to exist on its own (js code, test, views, demo page, css, etc):

- one
	- one.js (index.js)
	- one_test.js
	- fixtures.json
	- demo.html
	- one.handlebars
- other
	- other.js (index.js)
	- other_test.js
	- fixtures.json
	- demo.html
	- other.handlebars
- models
	- subfolder
		- submodel1.js
	- model1.js
	- model2.js
- fixtures
	- subfolder
		- submodel1.json
	- model1.json

We've found for large app with many reusable components, this organization is more maintainable, easier to navigate, decoupled and modular. These controls can be reused. In the Backbone style organization, it is much harder to reuse pieces across apps.

Memory Leaks

Big JS apps have memory leaks. Backbone does nothing to try to solve this problem. CanJS uses 2 clever techniques to virtually eliminate any possibility of a leak. This is well documented here, but I'll give a quick overview:

Its common while creating controls that you listen to events outside the control's element. For example, a menu control needs to listen to window click to know when to close itself. When the menu is destroyed, all event handlers that exist inside the control's element are unbound. But any event handlers that live outside (like the window click handler) are not unbound.

This creates a huge problem in long running large JS apps. Each un-cleaned up event handler has a closure that could reference an element, which means even that element can't be garbage collected:

// inside Backbone.View Tooltip code
$(window).bind('click', function(){
    // this element won't be removed from memory after the tooltip is gone
    if (!this.element.has( ev.target ) {
      this.element.remove();
    }
});

In Backbone, there is no solution for this. Memory leaks are common and will slow down your app and cause unexpected errors. In CanJS you can use templated event handlers to bind events to elements outside a control's element and they will all be automatically unbound when the control's element is unbound:

var Tooltip = can.Control({
  '{window} click': function( el, ev ) {
    // hide only if we clicked outside the tooltip
    if (!this.element.has( ev.target ) {
      this.element.remove();
    }
  }
})

Another leaky problem is related to large sets of data in any application. Big JS apps often grab large data sets from the server. If they are long-lived, sometimes that data becomes stale and is replaced by new data. If the data is kept in a store in memory, the app will quickly consume a lot of local memory, even though many of these objects in the store are no longer needed.

In Backbone there is no solution for this other than manually flushing out your local store when its too large.

In CanJS every model data object has a _bindings property that keeps track of how many times something has bound to a property change for this element. Any time this object is bound to, this property is incremented, and when it is unbound, it is decremented (including for live binding in a template). When this count goes down to 0, can.Model deletes this data from the store.

This means you if you have old stale data, it is automatically cleaned up in CanJS, leaving your memory footprint lighter.

Lacking Functionality

Backbone has no built in support for routing (creating back-button enabled web apps). You have to use another solution for that. CanJS has can.route, a built in system for simply adding route support. We are using it already for ICA and it made it very easy to add this functionality.

Backbone also lacks any built in templating engine. You have to integrate your own, and you only get very basic template rendering. Its also up to you to figure out how to build templates into a production file for better performance in production (otherwise each file will load individually, which is very bad).

CanJS supports EJS and Mustache templates out of the box. It also has built in support to integrate these templates with Steal's build system, making moving to production very simple and brainless. Mustache and EJS have built in live binding support in Can. Can also adds convenient view features that are not possible in Backbone, like handling deferreds that are pass into a view:

can.view( 'todos/todos.ejs', {
  todos: Todo.findAll(),
  user: User.findOne( { id: 5 } )
} ).then(function( frag ){
  document.getElementById( 'todos' )
          .appendChild( frag );
})

Easy Learning Curve

The reason people love Backbone and the main reason its so popular is because its very easy for a beginner to pick it up. By contrast Ember is notoriously difficult to learn and understand because its very different from vanilla jQuery development.

CanJS is VERY similar to Backbone on first glance. Controls organize event handlers, views render templates, models organize AJAX calls. And its all synchronous, unlike Ember.

So Can and Backbone are both equally simple for a beginning JS developer to grasp.

Size

Gzipped and compressed Backbone is ~9KB and Can is ~11KB. Amazing considering the amount of functionality Can has on top of backbone. By contrast, Ember is ~37KB.

Backbone comes as an all in one download. CanJS has a download builder where you pick just the comoponents you want, and it compiles them into a single minified script. Using this tool you can customize CanJS to not include components you aren't using, which isn't possible in Backbone.

Library support

Can lives on top of any major DOM library: YUI, Dojo, Zepto, jQuery, or Mootools. This is nice when working in an organization where more than one DOM lib is being used. Only one application API to learn.

Backbone requires jQuery or Zepto.

Extensibility

Sometimes its necessary to create features that extend the core functionality of the library. Backbone and Can are pretty even in this category. Both try to expose methods that might be useful hooks for additionall functionality.

For example, in Can, you can create a validations plugin by overwriting can.Observe.prototype.__set. The annotated source for this plugin is here.

The source of CanJS is written to be as readable and modular as possible, just like the apps built from it. Annotated source is here.

Sustainability

Frameworks come and go all the time. GWT made a lot of noise a few years ago and Google silently stopped working on it and has all but killed the project. Google could do the same with Angular at any time. Not that they will, but they don't have much of a vested need for it in the same way that Bitovi's business model depends on the success of CanJS.

This framework will be actively developed as long as Bitovi exists, and we have plans to exist for a while. DocumentCloud created Backbone, but their business is not Backbone development or consulting.

Support

You can hire the guys who made CanJS (us). We will answer questions instantly, fix bugs immediately, and even add features that your app needs. Your best bet for Backbone is to hire a consultant who has used the framework. The guys who made it aren't for hire. If you want support, you have to ask in a forum or IRC and hope for the best.

@cherifGsoul
Copy link

Thanks :)

@vivekp123
Copy link

Very useful, Thanks!!

@Rendez
Copy link

Rendez commented Dec 20, 2014

Excellent comparison. Can.JS does have a learning curve, but the initial concepts are much more friendly and make more sense after a few hours of diving into it.

For me another big reason to choose can is the availability of can.Component which work much like web components. Web components are native and it´s is the future of javascript web apps.

I found can.js to be built in a way that restricts the way to do things to adopt best practices of modularity, decoupling and garbage collection.

I personally won't go back to Backbone.js on the start of a new project.

@ymranx
Copy link

ymranx commented Jan 10, 2015

Thank you :) nice comparison. We've been developing very large scale JS app in CanJS since past 1.5 Yr. The initial reason to choose CanJS for our app was its rich feature like destroy(), straight forward loading of view, syntax like object creation much closer to plain JS, live data binding, dependency injection is extremely easy. If you are developing application which keeps growing with time canjs is a good choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment