Skip to content

Instantly share code, notes, and snippets.

@moschel
Last active March 19, 2018 21:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save moschel/5060784 to your computer and use it in GitHub Desktop.
Save moschel/5060784 to your computer and use it in GitHub Desktop.

CanJS and Backbone both provide structure for JavaScript applications through an MVC-like pattern.

CanJS

CanJS is a lightweight MVC framework built to provide structure for JavaScript applications. It is aimed at developers of any library who need a battle-hardened library that makes building complex applications easy. It is part of a larger offering named JavaScriptMVC from Bitovi (its creators) that has documentation, testing and dependency management tools.

Backbone

Backbone is a flexible, minimalist solution for seperating concerns in a JavaScript application. It provides just a little bit of structure to a jQuery application and has largely attracted developers who know just enough jQuery to get by and now want to organize their code a bit more.

Proven in Production

CanJS was originally a project named jQueryMX which provided the MVC functionality for JavaScriptMVC which is 5 years old. CanJS/jQueryMX has been used in many large, complex applications in its 5 years:

  • Norton eCommerce
  • MindJet
  • FrogOS
  • Apple Online Store
  • Bootswatchr
  • Cengage Mindtap

Backbone has been around for just over two years and in that time has been used in many applications/websites:

  • DocumentCloud
  • USAToday
  • Rdio
  • LinkedIn Mobile
  • Disqus
  • Khan Academy

One caveat is that Backbone is often used on brochure-type sites that would normally just use jQuery and are not actually applications.

Verdict: Draw

Maturity

The technology CanJS is built on is over 5 years old. CanJS has been at version 1.x for almost a year.

Backbone is at version 0.9.x which usually implies that the API could drastically change before they hit a 1.0 release.

Verdict: CanJS

Active Development

CanJS releases a new version about every 6 weeks.

Official Backbone updates are few and far between. Plugins for Backbone and unofficial fixes are used as stop gaps. For example it jumped from 0.5.3 in August 2011 to 0.9.0 in January 2012 with no updates in between (5 months). Then from 0.9.2 in March 2012 to 0.9.9 in December 2012 (9months).

Verdict: CanJS

Documentation

CanJS has an overview site at canjs.us with many examples and detailed API docs at donejs.com. Annotated source is also available.

Backbone has an overview/API site at Backbonejs.org and annotated source code. It has many more examples due to its popularity.

Verdict: Backbone

Community

CanJS is supported full-time by Bitovi, a JavaScript consultancy and a small, active community.

Backbone has a very large community.

Verdict: Draw

Size

Gzipped and compressed CanJS is ~11KB

Gzipped and compressed Backbone is ~9KB with Underscore.

By comparison, Ember is 37KB.

Verdict: Backbone

However, 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.

Templating

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 );
})

Backbone 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).

Verdict: CanJS

Performance

Control

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

This is because when creating controls, CanJS 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.

Templates

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.

Verdict: CanJs

Memory Safety

CanJS uses some clever techniques that virtually eliminate any possibility of having memory leaks. These are well documented here.

Templated Event Bindings

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();
    }
  }
})

Model Store

Another leaky problem is related to large sets of data stored in JS. 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.

CanJS has a model store that cleans up model instances that are no longer bound to anything. This also allows you to represent the same model in multiple places in your UI and have changes to that model properly reflected everywhere. Even though a model may be in the UI two or three times, only a single instance is created.

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, leaving you with a lighter memory footprint.

Backbone does nothing to try to solve this problem and often you are left with "zombie views" which are views that are not being used (dead), but still linger.

Verdict: CanJS

Library support

CanJS works with 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. There is only one application API to learn.

Backbone works with underscore and jQuery or Zepto.

Verdict: CanJS

Livebinding

Using CanJS, you only have to worry about how you represent data once, while creating your view.

Backbone has no concept of binding data object and their changes to your templates, so every single time data changes, you have to either a) manually trigger an event that re-renders entire views with the new data or b) write some "glue" code that specifically changes a small part of the DOM when the data changes like this:

'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. 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);

Verdict: CanJs

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.

Verdict: CanJS

Extensibility

Sometimes its necessary to create features that extend the core functionality of the library. Backbone and CanJS are pretty even in this category. Both try to expose methods that might be useful hooks for additional 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.

Verdict: Draw

Support

You can hire the developers that wrote CanJS (us) We will answer questions instantly, fix bugs immediately, and even add features that your app needs.

Your best bet for Backbone support is to hire a consultant who has used the framework, but the developers who made it aren't for hire. for support questions you would have to have to ask in a forum or IRC and hope for the best.

Verdict: CanJS

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, which has a confusing and difficult to debug asycnhronous event loop.

Verdict: Draw

@justinbmeyer
Copy link

  • makeFindAll
  • nested observes
  • computes
  • startBatch
  • 1st party plugins
  • works very well with amd / steal
  • works very well with jQuery++
  • deferreds support

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