框架操做DOM和原生js操做DOM比較

問題引出javascript

對於Angular和React操做DOM的速度,和原生js操做DOM的速度進行了一個比較:css

一個同窗作的demohtml

代碼以下:前端

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

運行結果:java

原生最快,爲何還要用框架?react

問題分析git

框架封裝了不少操做,爲了提高可維護性,會進行分層,只提供出API。不少應用其實相似,爲了提高可讀性可複用性,每每會分層、封裝,單一的簡單應用上性能上可能並不會有明顯提高。可是,對於複雜的須要不少模塊的應用,框架的優點就很明顯。在不用本身優化性能的同時,可維護性也較好。框架會盡最大程度保證性能。複雜的狀況,好比屢次更新一塊DOM,或者多塊DOM同時更新等等。github

React的VirtualDOMajax

若是沒有VirtualDOM的狀況下,想重置DOM,須要使用innerHTML.一個大型列表的數據若是都變動了,那麼重置全部DOM無可厚非,可是更多的狀況是,只有幾個數據變動了,這種狀況下,從新渲染大片DOM,就有些浪費了。數據庫

能夠比較下虛擬DOM和innerHTML的差別:

innerHTML: render html string O(template size) + 從新建立全部 DOM 元素 O(DOM size)
Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)能夠看出,虛擬DOM在渲染環節和比較環節都會比InnerHTML慢些。可是,與操做DOM比起來,js運算是快不少的了。因此這必定程度上,速度會快些。
因此,無論每次有多少數據變動,性能均可以接受,React也並無強調說必定比原生快這樣。
 
MVVM與Virtual DOM比較
MVVM採用的都是數據綁定:
經過Directive/Binding對象來觀察數據變化,有數據變化時改變對應的DOM顯示。Angular的
Dirty Checking:scope digest O(watcher out)+必要的DOM更新 O(DOM change)
Angular比較受詬病的地方,就在於任何小的變更,都有和watcher數量相關的性能代價。
MVVM在渲染列表時,每一行數據都有本身的做用域,一般是每行都有一個對應的的ViewModel實例。或者有的是利用原型繼承的scope對象,都是有代價的。通常ViewModel初始化都比Virtual DOM慢。
VirtualDOM的檢測是DOM側的,而ViewModel若是有數據更新,會銷燬全部實例,從新建立實例,再進行一次渲染。因此較React會慢些。對於React,就算是全新的數據,只要渲染以後的DOM沒變,不須要更新。
Angular提供了列表重繪的優化機制,就是讓框架有效的複用實例和DOM元素。例如數據庫裏面的同一個數據,前端兩次拉取會取到不一樣的值,可是他們有同樣的uid.能夠添加track by uid讓ng知道,這倆對象是同一份數據。原來實例的對象和DOM就能夠複用,只會更新變更的部分。上面的例子裏面若是加上track by $index的話,會快一些。能夠看看加了track by $index的運行結果:
沒錯,ng明顯快了不少。
應用過程當中,老是應該選擇合適的框架,若是可能,對其作一些優化。
通常狀況:
初始化渲染:Virtual DOM>Dirty Checking >=依賴收集
小範圍數據更新:依賴收集>>Virtual DOM+優化>Dirty Checking
大量數據更新:Dirty Checking+優化>=依賴收集+優化>Virtual DOM
 
 
參考: DOM操做速度
相關文章
相關標籤/搜索