Angular VS Knockout VS Ember VS React VS Mithril

JavaScript performance comparison

Revision 642 of this test case created by Eike Thies

Preparation code

<!-- Jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/1.13.0-beta.2/ember-template-compiler.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/1.13.0-beta.2/ember.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.2.0/mithril.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.11.10/vue.min.js"></script>
<!--[if IE]><script src="https://rawgit.com/jakearchibald/es6-promise/master/dist/es6-promise.min.js"></script><![endif]-->
<script src="https://rawgit.com/magnumjs/mag.js/master/dist/mag.0.11.9.min.js"></script>
<script src="https://cdn.rawgit.com/MaxArt2501/object-observe/master/dist/object-observe.min.js"></script>
<script src="https://rawgit.com/magnumjs/mag.js/master/src/mag.addons.js"></script>
<!-- Jquery -->
jQuery:
<div id="jq_test"></div>

<script>
var jqEl = $('#jq_test');
var children = '';
var jqPush = function (data) {
jqEl.append('<span>' + data + '</span>');
}
var jqClear = function () {
  jqEl.empty();
}
</script>

<!-- Angular -->
<div ng-app="app">
  Angular:
<!--  <span ng-controller="Ctrl" id="angList"><span ng-repeat="item in data track by $index">{{item}}</span></span> -->

<span ng-controller="Ctrl" id="angList"><span ng-repeat="item in data track by $index">{{::item}}</span></span>
</div>

<!-- Knockout -->
<div id="koapp">
  Knockout:
  <span data-bind="foreach: data"><span data-bind="text: $data"></span></span>
</div>

<!-- Ember -->

<div id="emapp"></div>
<script type="text/x-handlebars">
  Ember:
  <span>
    {{#each EMapp.data}}<span>{{this}}</span>{{/each}}
  </span>
</script>


<!-- React -->
<div id="react">
  React: <span id="reactMountNode"></span>
</div>


<!-- Mithril -->
<div id="mithrilapp">
  Mithril: <span id="mithrilMountNode"></span>
</div>


<!-- Vuejs -->
<div id="vuejs">
	Vuejs: <span id="vuejsMountNode"><span v-repeat="item: data">{{ item }}</span></span>
</div>

  <!-- MagJS -->
  <div id="magjsapp">
    MagJS: <div>
            <span id="magjsappMountNode"><span class="magjsdata"></span></span></div>
  </div>

<script>
window.getId = function(idx)
  {
    var d = Date.now() + "";
    return idx + "-" + d.substring(d.length - 6);
  };

angular.module('app', [])
.controller('Ctrl', ['$scope', Ctrl]);
function Ctrl($scope) {
    $scope.data = [];
};

var KOData = ko.observableArray();
var KOviewmodel = {data: KOData};

var ENV = {EXTEND_PROTOTYPES: false};

var ReactComponent = React.createClass({displayName: 'PerfTest',
  getInitialState: function() {
    return { data: [] };
  },
  render: function() {
    return (
       React.createElement("span", null,
        this.state.data.map(function(result) {
console.log(result);
          return React.createElement("span", null, result);
        })
      )
    );
  }
});

var MithrilData = new Array();
var mithapp = {
  controller: function() {
    this.data = MithrilData;
  },
  view: function(ctrl) {
    return m("span", [ctrl.data.map(function(datum) { return m('span', datum); })]);
  }
}

$(document).ready(function() {



  EMapp = Ember.Application.create({
    rootElement: '#emapp'
  });
  EMapp.data = Ember.A();

  window.EMclear = function () {
    EMapp.data.clear();
  };
  window.EMpush = function (data) {
    EMapp.data.pushObject(data);
  };


  angular.element(document).ready(function() {
    var ang_scope = $('#angList').scope();

    window.ANGclear = function(){
      ang_scope.data = [];
      ang_scope.$apply();
    };
    window.ANGpush = function(data){
      ang_scope.data.push(data);
    };
    window.ANGapply = function() {
      ang_scope.$apply();
    };
  });

  ko.applyBindings(KOviewmodel, document.getElementById('koapp'));
  window.KOclear = function (){
    KOData.splice(0, KOData().length);
  };
  window.KOpush = function (data){
    KOData.push(data);
  };


  var reactComp = React.render( React.createElement(ReactComponent, null), document.getElementById('reactMountNode'));

  window.RClear = function() {
    reactComp.setState({data: []})
  };

  window.RPush = function(entry) {
    var stateData = reactComp.state.data;
    stateData.push(entry);
    reactComp.setState({data: stateData});
  }

  m.module(document.getElementById("mithrilMountNode"), mithapp);
  window.Mclear = function() {
    m.startComputation();
    MithrilData.splice(0);
    m.endComputation();
  };

  window.Mpush = function(data) {
    MithrilData.push(data);
    m.redraw();
  }
  

  var vueInstance = new Vue({
	  el: '#vuejsMountNode',
	  data: {
		  data: []
	  }
  });
  
  window.VueClear = function(){
	  vueInstance.data = []
  };

  window.VuePush = function(data){
		vueInstance.data.push(data);
  };
  
  var MagJSData = ["TestMagJS"];

  var magjsdataNode = {
    view: function(state, props) {
      state.magjsdata = MagJSData;
    }
  }
  var magjsapp = {
    view: function(state, props) {
      state.div = mag.module("magjsappMountNode",magjsdataNode);
    }
  }
  mag.module("magjsapp",magjsapp);

  window.MagJSclear = function() {
    MagJSData.splice(0);
  };

  window.MagJSpush = function(data) {
    MagJSData.push(data);
  };

  window.MagJSforceRedraw = function(data) {
    mag.redraw(true);
  }
  MagJSclear();
});
</script>
    

Preparation code output

<!-- Jquery --> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/1.13.0-beta.2/ember-template-compiler.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/1.13.0-beta.2/ember.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.2.0/mithril.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.11.10/vue.min.js"></script> <!--[if IE]><script src="https://rawgit.com/jakearchibald/es6-promise/master/dist/es6-promise.min.js"></script><![endif]--> <script src="https://rawgit.com/magnumjs/mag.js/master/dist/mag.0.11.9.min.js"></script> <script src="https://cdn.rawgit.com/MaxArt2501/object-observe/master/dist/object-observe.min.js"></script> <script src="https://rawgit.com/magnumjs/mag.js/master/src/mag.addons.js"></script> <!-- Jquery --> jQuery: <div id="jq_test"></div> <script> var jqEl = $('#jq_test'); var children = ''; var jqPush = function (data) { jqEl.append('<span>' + data + '</span>'); } var jqClear = function () { jqEl.empty(); } </script> <!-- Angular --> <div ng-app="app"> Angular: <!-- <span ng-controller="Ctrl" id="angList"><span ng-repeat="item in data track by $index">{{item}}</span></span> --> <span ng-controller="Ctrl" id="angList"><span ng-repeat="item in data track by $index">{{::item}}</span></span> </div> <!-- Knockout --> <div id="koapp"> Knockout: <span data-bind="foreach: data"><span data-bind="text: $data"></span></span> </div> <!-- Ember --> <div id="emapp"></div> <script type="text/x-handlebars"> Ember: <span> {{#each EMapp.data}}<span>{{this}}</span>{{/each}} </span> </script> <!-- React --> <div id="react"> React: <span id="reactMountNode"></span> </div> <!-- Mithril --> <div id="mithrilapp"> Mithril: <span id="mithrilMountNode"></span> </div> <!-- Vuejs --> <div id="vuejs"> Vuejs: <span id="vuejsMountNode"><span v-repeat="item: data">{{ item }}</span></span> </div> <!-- MagJS --> <div id="magjsapp"> MagJS: <div> <span id="magjsappMountNode"><span class="magjsdata"></span></span></div> </div> <script> window.getId = function(idx) { var d = Date.now() + ""; return idx + "-" + d.substring(d.length - 6); }; angular.module('app', []) .controller('Ctrl', ['$scope', Ctrl]); function Ctrl($scope) { $scope.data = []; }; var KOData = ko.observableArray(); var KOviewmodel = {data: KOData}; var ENV = {EXTEND_PROTOTYPES: false}; var ReactComponent = React.createClass({displayName: 'PerfTest', getInitialState: function() { return { data: [] }; }, render: function() { return ( React.createElement("span", null, this.state.data.map(function(result) { console.log(result); return React.createElement("span", null, result); }) ) ); } }); var MithrilData = new Array(); var mithapp = { controller: function() { this.data = MithrilData; }, view: function(ctrl) { return m("span", [ctrl.data.map(function(datum) { return m('span', datum); })]); } } $(document).ready(function() { EMapp = Ember.Application.create({ rootElement: '#emapp' }); EMapp.data = Ember.A(); window.EMclear = function () { EMapp.data.clear(); }; window.EMpush = function (data) { EMapp.data.pushObject(data); }; angular.element(document).ready(function() { var ang_scope = $('#angList').scope(); window.ANGclear = function(){ ang_scope.data = []; ang_scope.$apply(); }; window.ANGpush = function(data){ ang_scope.data.push(data); }; window.ANGapply = function() { ang_scope.$apply(); }; }); ko.applyBindings(KOviewmodel, document.getElementById('koapp')); window.KOclear = function (){ KOData.splice(0, KOData().length); }; window.KOpush = function (data){ KOData.push(data); }; var reactComp = React.render( React.createElement(ReactComponent, null), document.getElementById('reactMountNode')); window.RClear = function() { reactComp.setState({data: []}) }; window.RPush = function(entry) { var stateData = reactComp.state.data; stateData.push(entry); reactComp.setState({data: stateData}); } m.module(document.getElementById("mithrilMountNode"), mithapp); window.Mclear = function() { m.startComputation(); MithrilData.splice(0); m.endComputation(); }; window.Mpush = function(data) { MithrilData.push(data); m.redraw(); } var vueInstance = new Vue({ el: '#vuejsMountNode', data: { data: [] } }); window.VueClear = function(){ vueInstance.data = [] }; window.VuePush = function(data){ vueInstance.data.push(data); }; var MagJSData = ["TestMagJS"]; var magjsdataNode = { view: function(state, props) { state.magjsdata = MagJSData; } } var magjsapp = { view: function(state, props) { state.div = mag.module("magjsappMountNode",magjsdataNode); } } mag.module("magjsapp",magjsapp); window.MagJSclear = function() { MagJSData.splice(0); }; window.MagJSpush = function(data) { MagJSData.push(data); }; window.MagJSforceRedraw = function(data) { mag.redraw(true); } MagJSclear(); }); </script>

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
Angular
ANGclear();
for (var i = 0; i < 100; i++) {
  ANGpush("nitem"+ getId(i));
}
ANGapply();
pending…
Knockout
KOclear();
for (var i = 0; i < 100; i++)
  KOpush("kitem"+ getId(i));
pending…
React
RClear();
for (var i = 0; i < 100; i++)
  RPush("ritem"+ getId(i));
pending…
Mithril
Mclear();
for (var i = 0; i < 100; i++)
  Mpush("mitem"+ getId(i));
pending…
vuejs
VueClear();
for (var i = 0; i < 100; i++)
  VuePush("vitem"+ getId(i));
pending…
Ember
EMclear();
for (var i = 0; i < 100; i++)
  EMpush("eitem"+ getId(i));
pending…
jQuery
jqClear();
for (var i = 0; i < 100; i++) {
  jqPush('jq-item'+ getId(i));
}
 
pending…
MagJS
MagJSclear();
for (var i = 0; i < 100; i++) {
  MagJSpush("mjsit"+getId(i));
}
MagJSforceRedraw();
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.

2 Comments

Daniel commented :

hi, consider replacing $apply with $evalAsync to increase angular performance and make it the top of the list framework :) like in revision 643 i published.

Daniel commented :

hi, again, the track by index is not very helpful when working with arrays, better use the property that actually changes like item in this case looking at the comparison new looks much more right to me.

jQuery:
Angular: {{::item}}
Knockout:
React:
Mithril:
Vuejs: {{ item }}
MagJS: