CanJS EJS Live Binding Performance

JavaScript performance comparison

Test case created by Curtis Cummings

Info

Comparison between CanJS, Backbone, Ember, YUI and Ember.

Test renders 100 circles and updates the content and style of each.

Based on demo here: http://jsfiddle.net/JMWf4/4/

Preparation code

<script src="http://underscorejs.org/underscore-min.js">
</script>
<script src="http://donejs.com/examples/todo/zepto/zepto.0.8-focusblur.js">
</script>
<script src="http://donejs.com/can/dist/edge/can.zepto.js">
</script>
<script src="http://backbonejs.org/backbone-min.js">
</script>
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js">
</script>
<script type="text/ejs" id="ejs-template">
  <% list(boxes, function( box ) { %>
    <div class="box-view">
      <div class="box" id="box-<%= box.count  %>" style="<%= box.style() %>">
        <%= box.content() %>
      </div>
    </div>
  <% }) %>
</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">
  <div class="box" {{bindAttr id="model.number" style="model.style"}}>
    {{ model.content }}
  </div>
</script>

<script type="text/x-template" id="knockout-template">
  <div data-bind="foreach: boxes">
    <div class="box-view">
      <div class="box" data-bind="style: {top: top(), left: left(), background: color()}, text: content()"></div>
    </div>
  </div>
</script>

<script type="text/x-yui-handlebars" id="yui-handlebars-template">
  <div class="box" id="box-{{number}}" style="top: {{top}}px; left: {{left}}px; background: rgb(0,0,{{color}});">
    {{content}}
  </div>
</script>
<div id="grid"></div>
<script>
Benchmark.prototype.setup = function() {
    window.N = 100;
        window.timeout = null;
        window.iterations = 1;
        window.reset = function() {
            $('#gridcan').empty();
            $('#gridko').empty();
            $('#gridb').empty();
            clearTimeout(timeout);    
          };
        (function() {
          var canIterations = 0,
              Box = function() {
              return {
                count: 0,
                top: function() {
                  return (Math.sin(this.count / 10) * 10) + 'px'
                },
                left: function() {
                  return (Math.cos(this.count / 10) * 10) + 'px'
                },
                color: function() {
                  return 'rgb(0,0,' + (this.count) % 255 + ')'
                },
                content: function() {
                  return this.attr('count') % 100
                },
                tick: function() {
                  this.attr('count', (this.attr('count') + 1));
                },
                style: function() {
                  this.attr('count');
                  return 'top: ' + this.top() + '; left: ' + this.left() + '; background: ' + this.color() + ';';
                }
              }
              };
              window.canInit = function() {
              boxes = new can.Observe.List();
              for (var i = 0; i < N; i++) {
                boxes.push(new Box());
              }
              $('#gridcan').append(can.view('ejs-template', {
                boxes: boxes
              }));
              };
              var canAnimate = function() {
              if (canIterations < iterations) {
                for (var i = 0; i < N; i++) {
                  boxes[i].tick();
                }
                window.timeout = _.defer(canAnimate);
              } else {
                deferred.resolve();
    return;
              }
              canIterations++;
              };
          window.runCan = function() {
            //reset();
            canIterations = 0;
            //canInit();
            canAnimate();
          };
        })();
       
        (function() {
       
          var backboneIterations = 0;
       
          var Box = Backbone.Model.extend({
       
            defaults: {
              top: 0,
              left: 0,
              color: 0,
              content: 0
            },
       
            initialize: function() {
              this.count = 0;
            },
       
            tick: function() {
              var count = this.count += 1;
              this.set({
                top: Math.sin(count / 10) * 10,
                left: Math.cos(count / 10) * 10,
                color: (count) % 255,
                content: count % 100
              });
            }
       
          });
       
       
          var BoxView = Backbone.View.extend({
       
            className: 'box-view',
       
            template: _.template($('#underscore-template').html()),
       
            initialize: function() {
              this.model.bind('change', this.render, this);
            },
       
            render: function() {
              this.$el.html(this.template(this.model.attributes));
              return this;
            }
       
          });
       
          var boxes;
       
          window.backboneInit = function() {
              boxes = _.map(_.range(N), function(i) {
                var box = new Box({
                  number: i
                });
                var view = new BoxView({
                  model: box
                });
                $('#gridb').append(view.render().el);
                return box;
              });
              };
       
          var backboneAnimate = function() {
              if (backboneIterations < iterations) {
                for (var i = 0, l = boxes.length; i < l; i++) {
                  boxes[i].tick();
                }
                window.timeout = _.defer(backboneAnimate);
              } else {
    deferred.resolve();
                return;
              }
              backboneIterations++;
              };
       
          window.runBackbone = function() {
            //reset();
            backboneIterations = 0
            //backboneInit();
            backboneAnimate();
          };
        })();
       
       
       
        (function() {
       
          var knockoutIterations = 0;
       
          var Box = function() {
              this.count = ko.observable(0);
              this.top = function() {
                return (Math.sin(this.count() / 10) * 10) + 'px'
              };
              this.left = function() {
                return (Math.cos(this.count() / 10) * 10) + 'px'
              };
              this.color = function() {
                return 'rgb(0,0,' + (this.count()) % 255 + ')'
              };
              this.content = function() {
                return this.count() % 100
              };
       
       
              this.tick = function() {
                this.count(this.count() + 1);
              }
              }
             
             
             
             
             
          var ViewModel = function(num) {
              this.num = num;
              this.boxes = ko.observableArray();
              for (var i = 0; i < num; i++) {
                this.boxes.push(new Box())
              }
              }
             
             
             
             
             
          var knockoutAnimate = function() {
              if (knockoutIterations < iterations) {
                for (var i = 0, l = vm.boxes().length; i < l; i++) {
                  vm.boxes()[i].tick();
                }
                window.timeout = _.defer(knockoutAnimate);
              } else {
    deferred.resolve();
                return;
              }
              knockoutIterations++;
              }
             
             
             
             
              window.knockoutInit = function(){
            //knockoutIterations = 0;
            $('#gridko').html($('#knockout-template').html());
            vm = new ViewModel(100)
            ko.applyBindings(vm, $('#gridko')[0]);
              }
   
          window.runKnockout = function() {
            knockoutIterations = 0;
            knockoutAnimate();
          };
       
        })();
        canInit();
        backboneInit();
        knockoutInit();
};

Benchmark.prototype.teardown = function() {
    window.reset()
};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
CanJS
// async test
window.runCan()
pending…
Backbone
// async test
window.runBackbone();
pending…
Knockout
// async test
window.runKnockout();
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment