问题引出
对于Angular和React操作DOM的速度,和原生js操作DOM的速度进行了一个比较:
一个同学做的demo
代码如下:
<!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"), template = document.getElementById("raw-template").innerHTML; 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"; }); } 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>
运行结果:
原生最快,为什么还要用框架?
问题分析
框架封装了很多操作,为了提升可维护性,会进行分层,只提供出API。很多应用其实类似,为了提升可读性可复用性,往往会分层、封装,单一的简单应用上性能上可能并不会有明显提升。但是,对于复杂的需要很多模块的应用,框架的优势就很明显。在不用自己优化性能的同时,可维护性也较好。框架会尽最大程度保证性能。复杂的情况,比如多次更新一块DOM,或者多块DOM同时更新等等。
React的VirtualDOM
如果没有VirtualDOM的情况下,想重置DOM,需要使用innerHTML.一个大型列表的数据如果都变更了,那么重置所有DOM无可厚非,但是更多的情况是,只有几个数据变更了,这种情况下,重新渲染大片DOM,就有些浪费了。
可以比较下虚拟DOM和innerHTML的差异: