• 框架操作DOM和原生js操作DOM比较


    问题引出

    对于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>
    DOM Render

    运行结果:

    原生最快,为什么还要用框架?

    问题分析

    框架封装了很多操作,为了提升可维护性,会进行分层,只提供出API。很多应用其实类似,为了提升可读性可复用性,往往会分层、封装,单一的简单应用上性能上可能并不会有明显提升。但是,对于复杂的需要很多模块的应用,框架的优势就很明显。在不用自己优化性能的同时,可维护性也较好。框架会尽最大程度保证性能。复杂的情况,比如多次更新一块DOM,或者多块DOM同时更新等等。

    React的VirtualDOM

    如果没有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
     
     
  • 相关阅读:
    十三周课程总结
    第十二周课程总结
    第十一周课程总结
    第十周java总结
    第九周课程总结&实验报告(七)
    第八周课程报告&&实验报告六
    第七次学习总结&&第五次实验报告
    第六次学习总结&&第四次实验总结
    同余&逆元简单总结
    原根&离散对数简单总结
  • 原文地址:https://www.cnblogs.com/linda586586/p/5122512.html
Copyright © 2020-2023  润新知