Last active
January 10, 2019 22:52
-
-
Save RichAyotte/a7b8780341d5e75beca7 to your computer and use it in GitHub Desktop.
Performance Comparison for React, Angular and Knockout
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html ng-app="test"> | |
<head> | |
<title>Performance Comparison for Knockout, Angular and React</title> | |
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.css" rel="stylesheet" /> | |
<style type="text/css"> | |
* { box-sizing:border-box; } | |
body { padding:30px 0; } | |
h2 { margin:0; margin-bottom:25px; } | |
h3 { margin:0; padding:0; margin-bottom:12px; } | |
.test-data { margin-bottom:3px; } | |
.test-data span { padding:3px 10px; background:#EEE; width:100%; float:left; cursor:pointer; } | |
.test-data span:hover { background:#DDD; } | |
.test-data span.selected { background:#3F7AD9; color:white; } | |
.time { font-weight:bold; height:26px; line-height:26px; vertical-align:middle; display:inline-block; cursor:pointer; text-decoration:underline; } | |
</style> | |
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> | |
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.3/angular.min.js"></script> | |
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/react/0.12.1/react.js"></script> | |
<script type="text/javascript"> | |
console.timeEnd("build"); | |
document.addEventListener("DOMContentLoaded", function() { | |
_knockout(); | |
_react(); | |
_raw(); | |
}); | |
_angular(); | |
function _buildData(count) { | |
count = count || 1000; | |
var adjectives = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"]; | |
var colours = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"]; | |
var nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"]; | |
var data = []; | |
for (var i = 0; i < count; i++) | |
data.push({id: i+1, label: adjectives[_random(adjectives.length)] + " " + colours[_random(colours.length)] + " " + nouns[_random(nouns.length)] }); | |
return data; | |
} | |
function _random(max) { | |
return Math.round(Math.random()*1000)%max; | |
} | |
function _knockout() { | |
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")); | |
} | |
function _angular(data) { | |
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; | |
}; | |
}); | |
} | |
function _react() { | |
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"; | |
}); | |
} | |
function _raw() { | |
var container = document.getElementById('raw') | |
, docFragment = document.createDocumentFragment() | |
, runRawNode = document.getElementById('run-raw') | |
, handler = function() { | |
var selected = container.querySelector('.selected'); | |
if (selected) { | |
selected.className = ''; | |
} | |
this.className = 'selected'; | |
} | |
; | |
runRawNode.addEventListener("click", function() { | |
var data = _buildData() | |
, date = new Date() | |
if (!container.hasChildNodes()) { | |
var containerWrap = document.createElement('div'); | |
container.appendChild(containerWrap); | |
for (var i = 0; i < data.length; i++) { | |
var div1 = document.createElement('div') | |
, div2 = document.createElement('div') | |
, span = document.createElement('span') | |
; | |
div1.className = 'row'; | |
div2.className = 'col-md-12 test-data'; | |
span.addEventListener('click', handler); | |
span.textContent = data[i].label; | |
div2.appendChild(span); | |
div1.appendChild(div2); | |
docFragment.appendChild(div1); | |
} | |
containerWrap.appendChild(docFragment); | |
container.appendChild(containerWrap); | |
} else { | |
for (var i = 0; i < data.length; i++) { | |
container.firstChild.childNodes[i].firstChild.firstChild.className = ''; | |
container.firstChild.childNodes[i].firstChild.firstChild.textContent = data[i].label; | |
} | |
} | |
runRawNode.textContent = (new Date() - date) + " ms"; | |
}); | |
} | |
ko.observableArray.fn.reset = function(values) { | |
var array = this(); | |
this.valueWillMutate(); | |
ko.utils.arrayPushAll(array, values); | |
this.valueHasMutated(); | |
}; | |
</script> | |
</head> | |
<body ng-controller="controller"> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-md-12"> | |
<h2>Performance Comparison for React, Angular and Knockout</h2> | |
</div> | |
</div> | |
<div class="col-md-3"> | |
<div class="row"> | |
<div class="col-md-7"> | |
<h3>React</h3> | |
</div> | |
<div class="col-md-5 text-right time" id="run-react">Run</div> | |
</div> | |
<div id="react"></div> | |
</div> | |
<div class="col-md-3"> | |
<div class="row"> | |
<div class="col-md-7"> | |
<h3>Angular</h3> | |
</div> | |
<div class="col-md-5 text-right time" id="run-angular" ng-click="run()">Run</div> | |
</div> | |
<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> | |
</div> | |
<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> | |
<div class="col-md-3"> | |
<div class="row"> | |
<div class="col-md-7"> | |
<h3>Raw</h3> | |
</div> | |
<div class="col-md-5 text-right time" id="run-raw">Run</div> | |
</div> | |
<div id="raw"></div> | |
</div> | |
</div> | |
<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> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment