Posted on January 26, 2015 by Chris Harrington

Performance of AngularJS, ReactJS and KnockoutJS Compared

I’ve recently written an article comparing React JS and Angular JS, and it’s been getting a fair bit of attention, so I figured I’d go one step further and show how well each of the two perform. I’ve also added in Knockout JS to the mix here just for comparison’s sake, and also some raw JavaScript DOM manipulation to give a base line as to how well things work across the board.

I’ve put together a quick page that allows you to see the rendering time difference between the three frameworks, plus the raw DOM manipulation. It’s what I’ve been using to figure out how long each framework takes to render large lists, as seen in the results table below. Take a look here.

Testing Methodology

This is by no means a scientific test as to how well each framework performs. That said, though, I tried to make it equal across all frameworks, so to that end, I’m going to walk through how I wrote each framework’s test below. Basically, I generate a list of 1000 items that get rendered into a ul tag, and then measure how long it takes by measuring dates before and after a render. The user is able to select an item in the list, too, so it’s not just a straight html render; there are events bound. It’s not a perfect test by any stretch, but it gets the point across.

Note: If you’re interested in this section at all, you’re going to need at least a passing understanding as to how to build something in each of the frameworks. Tutorials on React, Angular and Knockout are outside the scope of this article. I’ve written an article comparing Angular and React which shows the reader how to build typical things in each framework; you can see it here.

React JS

var Class = React.createClass({
    select: function(data) {
        this.props.selected = data.id;
        this.forceUpdate();
    },

    render: function() {
        var items = [];
        for (var i = 0; i < this.props.data.length; i++) {
            items.push(React.createElement("div", { className: "row" },
                React.createElement("div", { className: "col-md-12 test-data" },
                    React.createElement("span", { className: this.props.selected === this.props.data[i].id ? "selected" : "", onClick: this.select.bind(null, this.props.data[i]) }, this.props.data[i].label)
                )
            ));
        }

        return React.createElement("div", null, items);
    }
});

var runReact = document.getElementById("run-react");
runReact.addEventListener("click", function() {
    var data = _buildData(),
        date = new Date();

    React.render(new Class({ data: data, selected: null }), document.getElementById("react"));
    runReact.innerHTML = (new Date() - date) + " ms";
});
[/code]

So as a quick run down, when the user initiates a run, I build up a new list of 1000 items, make a note of the current time, render the React class, and then measure how long it took and write that time to the page. The class itself is straightforward, with a quick loop in the render function to push all of the created li tags into an array, and then rendering that array into a containing div. There's an onClick binding set on the li to select it, too.

Angular JS

[code language="html"] <div> <div class="row" ng-repeat="item in data"> <div class="col-md-12 test-data"> <span ng-class="{ selected: item.id === $parent.selected }" ng-click="select(item)">{{item.label}}</span> </div> </div> </div>
angular.module("test", []).controller("controller", function($scope) {
    $scope.run = function() {
        var data = _buildData(),
        date = new Date();

        $scope.selected = null;
        $scope.$$postDigest(function() {
            document.getElementById("run-angular").innerHTML = (new Date() - date) + " ms";
        });

        $scope.data = data;
    };

    $scope.select = function(item) {
        $scope.selected = item.id;
    };
});

The whole performance page is marked as an Angular app, so building up the module is pretty straightforward. Of note here is the $$postDigest hook, which is an internal Angular construct. It allows us to specify a callback to fire after a digest cycle has completed.

Knockout JS

<div id="knockout" class="col-md-3">
    <div class="row">
        <div class="col-md-7">
            <h3>Knockout</h3>
        </div>
        <div class="col-md-5 text-right time" id="run-knockout" data-bind="click: run">Run</div>
    </div>
    <div data-bind="foreach: data">
        <div class="row">
            <div class="col-md-12 test-data">
                <span data-bind="click: $root.select.bind($root, $data), text: $data.label, css: { selected: $data.id === $root.selected() }"></span>
            </div>
        </div>
    </div>
</div>
ko.applyBindings({
    selected: ko.observable(),
    data: ko.observableArray(),

    select: function(item) {
        this.selected(item.id);
    },

    run: function() {
        var data = _buildData(),
        date = new Date();

        this.selected(null);
        this.data(data);
        document.getElementById("run-knockout").innerHTML = (new Date() - date) + " ms";
    }
}, document.getElementById("knockout"));

Here, we're using an anonymous template for the Knockout bindings. We set up a click, text and css binding for indicating which item is selected, and a foreach loop in the Knockout html to actually do the rendering.

Raw DOM Manipulation

I added raw DOM manipulation to give a baseline as to how to run the same test without any fancypants framework.

<script type="text/html" id="raw-template">
    <div class="row">
        <div class="col-md-12 test-data">
            <span class="{{className}}">{{label}}</span>
        </div>
    </div>
</script>

var template = document.getElementById("raw-template").innerHTML,
container = document.getElementById("raw");

document.getElementById("run-raw").addEventListener("click", function() {
var data = _buildData(),
date = new Date(),
html = "";

for (var i = 0; i < data.length; i++) { var render = template; render = render.replace("{{className}}", ""); render = render.replace("{{label}}", data[i].label); html += render; } container.innerHTML = html; var spans = container.querySelectorAll(".test-data span"); for (var i = 0; i < spans.length; i++) spans[i].addEventListener("click", function() { var selected = container.querySelector(".selected"); if (selected) selected.className = ""; this.className = "selected"; }); document.getElementById("run-raw").innerHTML = (new Date() - date) + " ms"; }); [/code]

For the raw section, I'm grabbing the template from a script tag (marked with type text/html) and replace some key words wrapped in double curly braces with the appropriate text. Click events are hooked up via jquery.

Results

I ran all the tests in in Chrome 39.0.2171.95, Firefox 34.0.5 and Safari 7.0.2 by opening the test page and clicking the run button for each framework ten times. If you're interested in the raw data, I've made it available here. There are some fancy charts below which show the results.

Analysis

On average, React is the fastest at rendering a list of 1000 items. It's a surprising result, because I would have expected raw DOM manipulation to be the fastest, as there's nothing to hook up and so on. There's an interesting outlier in the Chrome test in that the second run of the React test takes a very long time to run: almost 600ms. It happens every time the second test is run in Chrome after a fresh reload and I'm not sure why. Raw DOM manipulation comes in second, which doesn't surprise me, as it's obviously much lighter than either Angular or Knockout, with nothing else to hook up or take care of behind the scenes. Angular comes in third, and Knockout comes in last. Knockout takes an especially long time in Firefox (~420ms). On a related note, Safari is the best browser across the board for all of the tested frameworks, but slowest for the raw test.

If performance is your primary motivation, React is a good choice for a framework, especially if you're showing large amounts of data. I understand that this example is relatively contrived, as nobody in their right mind is going to be showing 1000 items all at once, but I feel it's indicative as to which framework performs better as a whole. This article was originally posted on Code Mentor.

gisonline-me-gray