Ember Bindings vs. Knockout.js: A Benchmark

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

by KeithPepin

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://cloud.github.com/downloads/emberjs/ember.js/ember-0.9.4.min.js"></script>
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js"></script>

<p>Folks often ask about how the difference between Knockout's 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 500 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.</p>

<p><strong>Note: </strong> that this is just a simple benchmark: you wouldn't be doing this in an actual app.</p>

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

<!-- knockout -->
<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="text/x-handlebars" id="handlebars-template" data-template-name="box">
    <div class="box" {{bindAttr id="model.number" style="model.style"}}>
      {{ model.content }}       
    </div>
</script>

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 Ember implementation:
(function() {

    var Box = Ember.Object.extend({

        top: 0,
        left: 0,
        content: 0,
        count: 0,

        tick: function() {
            var count = this.get('count') + 1;
            this.set('count', count);
            this.set('top', Math.sin(count / 10) * 10);
       ...