CanJS EJS Live Binding Performance

JavaScript performance comparison

Test case created by Curtis Cummings

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

<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>

Test runner

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

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
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.

0 Comments