Backbone Events vs. Ember Bindings: A Benchmark

Animating 100 (or N) circles with your standard Backbone events and Ember bindings.

by scispear

HTML

<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script src="http://cloud.github.com/downloads/emberjs/ember.js/ember-0.9.8.min.js"></script>
<script src="https://raw.github.com/SteveSanderson/knockout/master/build/output/knockout-latest.debug.js"></script>

<p>
    Folks often ask about how the difference between Backbone and Ember's approaches to model/view binding play out in web apps. This script uses the latest Backbone.js and Ember.js to animate 100 circles (or more -- change "N" in the JavaScript), so that you can see the responsiveness after you click the button, and visualize the speed difference. The Ember team is aware that in very performance-sensitive situations, like this example, its binding implementation may be too much overhead. They have reviewed this example, and hope to be able to remove this difference in the future. Note that this is just a simple benchmark: you wouldn't be doing this in an actual app.
</p>

<button onclick="runBackbone()">Animate with Backbone</button>
<button onclick="runEmber()">Animate with Ember</button>
<button onclick="runKnockout()">Animate with Knockout</button>

<div id="grid" data-bind="template: { name: 'box-template' }">
</div>

<script type='x-template' id='box-template'>
    <!-- ko foreach:boxes -->
    <div class="box-view" data-bind="">
    <div class="box" data-bind='style: { top: topAsString(), left: leftAsString() , backgroundColor: colorAsRGB() }, text: content()'></div>
    </div>
        <!-- /ko -->
</script>

<script type="x-template" id="underscore-template">
  <div class="box" id="box-<%= number %>" style="top: <%= top %>px; left: <%= left %>px; background: rgb(0,0,<%= color %>);">
    <%= content %>       
  </div>
</script>
        
<script type="text/x-handlebars" id="handlebars-template" data-template-name="box">
  ...

CSS

p {
  font: 12px/16px Arial;
  margin: 10px 10px 15px;    
}

button {
  font: bold 14px/14px Arial;  
  margin-left: 10px;
}

#grid {
  margin: 10px;   
}

.box-view {
  width: 20px; height: 20px;
  float: left;
  position: relative;
  margin: 8px;    
}

.box {
  border-radius: 100px;
  width: 20px; height: 10px;
  padding: 5px 0;
  color: #ccc;
  font: 10px/10px Arial;
  text-align: center;
  position: absolute;
}

JavaScript

// Change N to change the number of drawn circles.
var N = 100;

// The Knockout implementation:
(function() {

    var BoxViewModel = function() {

        this.top = ko.observable(0);
        this.left = ko.observable(0);
        this.content = ko.observable(0);
        this.count = ko.observable(0);
        this.number = ko.observable(0);
        this.color = ko.observable('');
        this.colorAsRGB = ko.computed(function() {
            return 'rgb(0,0,' + this.color() + ')';
        }, this);
        this.topAsString = ko.computed(function() {
            return this.top() + 'px';
        }, this);
        this.leftAsString = ko.computed(function() {
            return this.left() + 'px';
        }, this);

        this.tick = function() {
            var count = this.count() + 1;
            this.count(count);
            this.top(Math.sin(count / 10) * 10);
            this.left(Math.cos(count / 10) * 10);
            this.color(count % 255);
            this.content(count % 100);
        };
    };

    var boxes;

    var knockoutInit = function() {
        boxes = ko.observableArray(_.map(_.range(N), function(i) {
            var boxViewModel = new BoxViewModel();
            boxViewModel.number(i);
            return boxViewModel;
        }));

        ko.applyBindings({
            boxes: boxes
        });
    };

    var knockoutAnimate = function() {
        for (var i = 0, l = boxes().length; i < l; i++) {
            boxes()[i].tick();
        }
        window.timeout = _.defer(knockoutAnimate);
    };

    window.runKnockout = function() {
        reset();
        knockoutInit();
        knockoutAnimate();
    };

})();

// The Backbone implementation:
(function() {

    var Box = Backbone.Model.extend({

        defaults: {
            top: 0,
            left: 0,
            color: 0,
            content: 0
        },

        initialize: function() {
            this.count = 0;
        },

        tick: function() {
            var count =...