Angular VS Knockout VS Ember VS React VS Mithril VS Mercury VS Ractive

JavaScript performance comparison

Revision 795 of this test case created by abernh

Preparation code


<!-- Jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.2.0/ember.prod.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.2.0/ember-template-compiler.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.11.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.1.22/mithril.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.10.5/vue.min.js"></script>
<script src="http://cdn.ractivejs.org/latest/ractive.min.js"></script>
<!-- Mercury -->
<!-- script src="http://raynos.github.io/mercury/index.js"></script -->

<!-- Angular -->
<div ng-app="ngApp">
    Angular 1.4.8 (using track by $index):
    <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>

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

<!-- Ember -->

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

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

<!-- Mercuryjs -->
<div id="mercuryjs">
    Mercuryjs: <span id="mercuryjsMountNode"></span>
</div>

<!-- Ractive -->
<div id="ractive">
    Ractive: <span id="ractiveMountNode"></span>
</div>

<script>

    var timeout = 0;

    // ----------------- Ang ------------------------

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

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

    var ENV = {EXTEND_PROTOTYPES: false};

    // ----------------- React ------------------------

    var ReactData = [];
    var ReactUpdate = null;
    var ReactComponent = React.createClass({
        displayName: 'PerfTest',
        getInitialState: function () {
            return {data: ReactData};
        },
        componentDidMount: function () {
            ReactUpdate = this._onData;
        },
        render: function () {
            return (
                    React.DOM.span(null,
                            this.state.data.map(function (result) {
                                return React.DOM.span(null, result);
                            })
                    )
            );
        },
        _onData: function () {
            this.setState({data: ReactData});
        }
    });

    // ----------------- Mithril ------------------------
    var MithrilData = [];
    var mithapp = {
        controller: function () {
        },
        view: function (ctrl) {
            return m("span", [MithrilData.map(function (datum) {
                return m('span', datum);
            })]);
        }
    };

    // ----------------- Mercury ------------------------

    window.mercuryPush = function () {
    };
    window.mercuryClear = function () {
    };
    try {
        var h = mercury.h;
        var mdata = mercury.array([]);

        function mrender(data) {
            return h("span", data.map(function (datum) {
                return h('span', datum)
            }));
        }

        // Mercury test calls
        mercury.app(document.getElementById('mercuryjsMountNode'), mdata, mrender);
        window.mercuryClear = function () {
            mdata.splice(0, mdata.getLength())
        };

        window.mercuryPush = function (data) {
            mdata.push(data)
        }
    } catch (err) {
        console.log("mercury tests not loaded.", err);
    }

    // ----------------- Reactive------------------------



    // ############### doc ready : render ############################

    $(document).ready(function () {

        // ----------------- Ang ------------------------
        angular.element(document).ready(function () {

            var ang_scope = $('#angList').scope();

            window.ANGclear = function () {
                ang_scope.data.splice(0, ang_scope.data.length);
            };
            window.ANGpush = function (data) {
                ang_scope.data.push(data);
                if (Date.now() > (timeout + 16)) {
                    window.setTimeout(function () {
                        ang_scope.$digest();
                        //console.log("triggered digest");
                    }, 0);
                    timeout = Date.now() + 16;
                }
            };
        });

        // ----------------- KO ------------------------

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


        var KODataRL = ko.observableArray();
        KODataRL.extend({rateLimit: 0});
        var KOviewmodelRL = {data: KODataRL};

        ko.applyBindings(KOviewmodelRL, document.getElementById('koappRL'));
        window.KOclearRL = function () {
            KODataRL.splice(0, KOData().length);
        };
        window.KOpushRL = function (data) {
            KODataRL.push(data);
        };

        // ----------------- Ember ------------------------

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

        var emberTimeout,
            emberDigestRunner = null,
            emberDigest = function(){
                var tmpNow = Date.now();

                if(emberTimeout){
                    if(emberTimeout < tmpNow + 32){


                        Ember.endPropertyChanges();
                        
                        emberTimeout = null;
                        window.clearTimeout(emberDigestRunner);
                    }else{
                        if(emberDigestRunner) return;
                        emberDigestRunner = window.setTimeout(function(){
                            emberDigest();
                        }, 32);
                        return;
                    }
                }

                if(!emberTimeout){
                    emberTimeout = tmpNow;
                    Ember.beginPropertyChanges();
                }
            };

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

        // ----------------- React ------------------------
        var reactComp = ReactComponent();
        React.renderComponent(reactComp, document.getElementById('reactMountNode'));

        window.RClear = function () {
            ReactData.splice(0, ReactData.length);
            ReactUpdate();
        };

        window.RPush = function (data) {
            ReactData.push(data);
            ReactUpdate();
        };

        // ----------------- Mithril ------------------------

        m.module(document.getElementById("mithrilMountNode"), mithapp);
        window.Mclear = function () {
            //MithrilData = [];
            MithrilData.splice(0, MithrilData.length);
        };

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

            if (Date.now() > (timeout + 16)) {
                window.setTimeout(function () {
                    m.startComputation();
                    m.endComputation();
                }, 0);

                timeout = Date.now();
            }

        };

        // ----------------- Vue ------------------------

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

        window.VueClear = function () {
            //vueInstance.data = [];
            vueInstance.data.splice(0, vueInstance.data.length);
        };

        window.VuePush = function (data) {
            vueInstance.data.push(data);
        };

        // ----------------- Reactive ------------------------

        var RactiveData = [];
        window.RactiveClear = function () {
            RactiveData.splice(0, ReactData.length);
        };
        window.RactivePush = function (data) {
            RactiveData.push(data);
        };

        var RactiveComponent = new Ractive({
            el: 'ractiveMountNode',
            template: '{{#data}}{{.}}{{/data}}',
            data: {data: RactiveData}
        });
    });

</script>
    

Preparation code output

<!-- Jquery --> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.2.0/ember.prod.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.2.0/ember-template-compiler.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.11.2/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.1.22/mithril.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.10.5/vue.min.js"></script> <script src="http://cdn.ractivejs.org/latest/ractive.min.js"></script> <!-- Mercury --> <!-- script src="http://raynos.github.io/mercury/index.js"></script --> <!-- Angular --> <div ng-app="ngApp"> Angular 1.4.8 (using track by $index): <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> <div id="koappRL"> Knockout: <span data-bind="foreach: data"><span data-bind="text: $data"></span></span> </div> <!-- Ember --> <div id="emapp"> <script type="text/x-handlebars"> Ember: <span> {{#each EMapp.data}}<span>{{this}}</span>{{/each}} </span> </script> </div> <!-- 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> <!-- Mercuryjs --> <div id="mercuryjs"> Mercuryjs: <span id="mercuryjsMountNode"></span> </div> <!-- Ractive --> <div id="ractive"> Ractive: <span id="ractiveMountNode"></span> </div> <script> var timeout = 0; // ----------------- Ang ------------------------ var ngApp = angular.module('ngApp', []); ngApp.controller('Ctrl', function ($scope) { $scope.data = []; }); // ----------------- KO ------------------------ var KOData = ko.observableArray(); var KOviewmodel = {data: KOData}; var ENV = {EXTEND_PROTOTYPES: false}; // ----------------- React ------------------------ var ReactData = []; var ReactUpdate = null; var ReactComponent = React.createClass({ displayName: 'PerfTest', getInitialState: function () { return {data: ReactData}; }, componentDidMount: function () { ReactUpdate = this._onData; }, render: function () { return ( React.DOM.span(null, this.state.data.map(function (result) { return React.DOM.span(null, result); }) ) ); }, _onData: function () { this.setState({data: ReactData}); } }); // ----------------- Mithril ------------------------ var MithrilData = []; var mithapp = { controller: function () { }, view: function (ctrl) { return m("span", [MithrilData.map(function (datum) { return m('span', datum); })]); } }; // ----------------- Mercury ------------------------ window.mercuryPush = function () { }; window.mercuryClear = function () { }; try { var h = mercury.h; var mdata = mercury.array([]); function mrender(data) { return h("span", data.map(function (datum) { return h('span', datum) })); } // Mercury test calls mercury.app(document.getElementById('mercuryjsMountNode'), mdata, mrender); window.mercuryClear = function () { mdata.splice(0, mdata.getLength()) }; window.mercuryPush = function (data) { mdata.push(data) } } catch (err) { console.log("mercury tests not loaded.", err); } // ----------------- Reactive------------------------ // ############### doc ready : render ############################ $(document).ready(function () { // ----------------- Ang ------------------------ angular.element(document).ready(function () { var ang_scope = $('#angList').scope(); window.ANGclear = function () { ang_scope.data.splice(0, ang_scope.data.length); }; window.ANGpush = function (data) { ang_scope.data.push(data); if (Date.now() > (timeout + 16)) { window.setTimeout(function () { ang_scope.$digest(); //console.log("triggered digest"); }, 0); timeout = Date.now() + 16; } }; }); // ----------------- KO ------------------------ ko.applyBindings(KOviewmodel, document.getElementById('koapp')); window.KOclear = function () { KOData.splice(0, KOData().length); }; window.KOpush = function (data) { KOData.push(data); }; var KODataRL = ko.observableArray(); KODataRL.extend({rateLimit: 0}); var KOviewmodelRL = {data: KODataRL}; ko.applyBindings(KOviewmodelRL, document.getElementById('koappRL')); window.KOclearRL = function () { KODataRL.splice(0, KOData().length); }; window.KOpushRL = function (data) { KODataRL.push(data); }; // ----------------- Ember ------------------------ EMapp = Ember.Application.create({ rootElement: '#emapp' }); EMapp.data = Ember.A(); var emberTimeout, emberDigestRunner = null, emberDigest = function(){ var tmpNow = Date.now(); if(emberTimeout){ if(emberTimeout < tmpNow + 32){ Ember.endPropertyChanges(); emberTimeout = null; window.clearTimeout(emberDigestRunner); }else{ if(emberDigestRunner) return; emberDigestRunner = window.setTimeout(function(){ emberDigest(); }, 32); return; } } if(!emberTimeout){ emberTimeout = tmpNow; Ember.beginPropertyChanges(); } }; window.EMclear = function () { EMapp.data.clear(); //emberDigest(); }; window.EMpush = function (data) { EMapp.data.pushObject(data); //emberDigest(); }; // ----------------- React ------------------------ var reactComp = ReactComponent(); React.renderComponent(reactComp, document.getElementById('reactMountNode')); window.RClear = function () { ReactData.splice(0, ReactData.length); ReactUpdate(); }; window.RPush = function (data) { ReactData.push(data); ReactUpdate(); }; // ----------------- Mithril ------------------------ m.module(document.getElementById("mithrilMountNode"), mithapp); window.Mclear = function () { //MithrilData = []; MithrilData.splice(0, MithrilData.length); }; window.Mpush = function (data) { MithrilData.push(data); if (Date.now() > (timeout + 16)) { window.setTimeout(function () { m.startComputation(); m.endComputation(); }, 0); timeout = Date.now(); } }; // ----------------- Vue ------------------------ var vueInstance = new Vue({ el: '#vuejsMountNode', data: { data: [] } }); window.VueClear = function () { //vueInstance.data = []; vueInstance.data.splice(0, vueInstance.data.length); }; window.VuePush = function (data) { vueInstance.data.push(data); }; // ----------------- Reactive ------------------------ var RactiveData = []; window.RactiveClear = function () { RactiveData.splice(0, ReactData.length); }; window.RactivePush = function (data) { RactiveData.push(data); }; var RactiveComponent = new Ractive({ el: 'ractiveMountNode', template: '{{#data}}{{.}}{{/data}}', data: {data: RactiveData} }); }); </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" + Date.now());
pending…
Knockout
KOclear();
for (var i = 0; i < 100; i++)
  KOpush("kitem" + Date.now());
pending…
Mithril
Mclear();
for (var i = 0; i < 100; i++)
  Mpush("mitem" + Date.now());
pending…
vuejs
VueClear();
for (var i = 0; i < 100; i++)
  VuePush("vitem" + Date.now());
pending…
Ember
EMclear();
for (var i = 0; i < 100; i++)
  EMpush("eitem" + Date.now());
pending…
Ractive
RactiveClear();
for (var i = 0; i < 100; i++)
  RactivePush("aitem" + Date.now());
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

Angular 1.4.8 (using track by $index): {{item}}
Knockout:
Knockout:
React:
Mithril:
Vuejs: {{ item }}
Mercuryjs:
Ractive: