zoukankan      html  css  js  c++  java
  • JS性能优化——DOM编程

    浏览器中的DOM  天生就慢

    DOM是个与语言无关的API,它在浏览器中的接口却是用JavaScript实现的。客户端脚本编程大多数时候是在个底层文档打交道,DOM就成为现在JavaScript编码中的重要部分。

    DOM访问和修改

    ECMAScript 每次访问DOM 都会产生性能损耗。

    修改元素则更为昂贵,因为它会导致浏览器重新计算页面的几何变换。

    最坏的情况是在循环中访问或修改元素,尤其是对HTML元素集合循环操作。

    function innerHtmlLoop(){
        for(var count = 0; count <15000; count++){
                document.getElementById('here').innerHTML +="a";
        }
    }

    这个函数循环修改页面元素的内容,每次循环迭代,该元素都被访问两次:一次读取innerHtml的属性值,另一次重写它。

    换一种效率更高的方法,用局部变量存储修改中的内容,再循环结束后一次性写入:

    function innerHtmlLoop(){
        var content = '';
        for(var count = 0; count <15000; count++){    
            count += 'a';
        }
        document.getElementById('here').innerHTML += content;
    }

    这种方式比上边的快了155倍。

    访问DOM的次数越多,代码的运行速度越慢。因此,通用的经验法则是:减少访问DOM的次数,把运算尽量留在ECMAScript这一端处理。

    innerHTML对比DOM方法:推荐使用innerHTML 而不是原生DOM方法生成HTML,绝大部分浏览器中都是innerHTML运行的更快。但是对于大多数日常的操作而言,并没有太大的区别,所以根据可读性、稳定性、团队习惯、代码风格来综合决定使用哪种方式。

    节点克隆:element.cloneNode()(element表示已有节点)替代document.createElement()。   在大多数浏览器中节点克隆更有效率,但是也不是特别明显。

    HTML集合:是包含了DOM节点引用的类数组对象,eg:document.getElementsByName();...或者:document.images页面中所有的img元素document.links所有a元素...

          返回值为HTML集合对象,是个类数组的列表。但是并不是真正的数组(因为没有slice和push之类的方法),但是提供了一个类似数组中的length的属性,并且还能以数字索引的方式访问列表中的元素。

          遍历这种类数组的集合,读取元素集合的length属性会引发集合进行更新,这在所有的浏览器中都有明显的性能问题,优化方法:将集合的长度缓存到循环外的局部变量中,然后在循环的条件退出语句中使用该变量:

    function loopCacheLengthCollection(){
        var coll = document.getElementsByTagName('div');  //这里的coll是集合 类数组
            len = coll.length;     //将集合的长度缓存到局部变量len中
        for(var count = 0; count < len; count++){    //不要在这里写 count < coll.length,会明显影响性能 , 
                                  //如果coll是数组,那么 count < coll.length 对性能影响并不大
    /* 代码处理 */ } }

    遍历DOM:可以使用document.querySelector('.myclass')的方法来查询整个文档,活通过elref.querySelector('.myclass')在子树中进行查询,这里的elref是一个DOM元素的引用。

    重绘与重排

    浏览器在下载完页面中的所有组件---HTML标记、JavaScript、css、图片,之后会解析并生成两个内部数据结构:

    DOM树:表示页面结构

    渲染树:表示DOM节点如何显示

    重排何时发生

        添加或删除可见的DOM元素;

        元素位置改变;

        元素尺寸改变(内外边距,边框厚度,宽高等);

        内容改变;文本改变或者图片被另一个不同尺寸的图片替代

        页面渲染器初始化;

        浏览器窗口尺寸改变

    渲染树变化的排队与刷新

        offsetTop, offsetLeft, offsetWidth, offsetHeight

        scrollTop, scrollLeft, scrollWidth, scrollHeight

        clientTop, clientLeft, clientWidth, clientHeight

        getComputedStyle()(currentStyle in IE)

      这些方法需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并处罚重排以返回正确的值,在修改样式的过程中,最好避免使用上边列出的属性

    var computed,
        tmp = '',
        bodystyle = document.body.style;
    if(document.body.currentStyle){ computed = document.body.currentStyle; } else{ computed = document.defaultView.getComputedStyle(document.body,''); } //修改同一属性低效的方式 //然后获取样式信息 bodystyle.color = 'red'; tmp = computed.backgroundColor; bodystyle.color = 'white'; tmp = computed.backgroundImage; bodystyle.color = 'green'; tmp = computed.backgroundAttachment; //每次修改够都读取一个computed样式属性。读取的属性backgroundColor、backgroundImage、backgroundAttachment都与改变的颜色无关。然而浏览器却需要刷新渲染队列并重排,因为compited的样式属性被请求了。 //更有效的方法,性能更快。如下: bodystyle.color = 'red'; bodystyle.color = 'white'; bodystyle.color = 'green'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment;

    最小化重绘和重排:重绘和重排可能代价非常昂贵,因此减少此类操作的发生。可以合并多次对DOM和样式的修改,然后依次处理掉。

    var el = document.getElementById('mydiv');
    el.style.borderLeft = '1px';
    el.style.borderRight = '2px';
    el.style.padding = '5px';
    //最糟糕情况下回导致浏览器触发三次重排。大部分浏览器为此做了优化,只会触发一次重排,但是如果在上边代码执行时,有其他代码请求布局信息,那么就会导致三次重排
    //而且这段代码四次请求DOM,可以被优化:
    
    var el = document.getElementById('mydiv');
    el.style.cssText = 'boeder-left: 1px; border-right: 2px; padding: 5px;';//如果不想覆盖原有的样式 可以写  el.style.cssText += ';boeder-left: 1px;';
    //修改css的class名称,更易于维护,可能会带来轻微的性能问题,因为改变类时需要检查级联样式。
    var el = document.getElementById('mydiv');
    el.className = 'active';

    批量修改DOM:当你对DOM元素进行一系列操作时,可以通过下边的步骤来减少重绘和重排:

          1、使元素脱离文档流

          2、对其应用多重改变

          3、把元素带回文档中。

          该过程会触发两次重排(①和③)。但是如果你忽略这两个步骤,那么在第二步所产生的任何修改都会触发一次重排

            使DOM脱离文档的三种基本方法:

              ①隐藏元素,应用修改,重新显示

    //为了演示脱离文档的操作,考虑下边的链接列表,它必须更新更多的信息
    <ul id = "mylist">
        <li><a href = "http://phpied.com">Stoyan</a></li>
        <li><a href = "http://julienlecomte.com">Stoyan</a></li>
    </ul>
    //假设附加数据已经存储在一个对象中,并要插入列表。这些数据定义如下:
    
    var data = [
    {
    "name": "Nicholas",
    "url": "http://nczonline.net"
    },
    {
    "name": "Ross",
    "url": "http://techfoolery.com"
    }
    ];
    //下面是一个用来更新指定节点数据的通用函数:
    function appendDataToElement(appendToElement, data){
        var a, li;
        for(var i = 0; max = data.length; i++){
            a = document.createElement('a');
            a.href = data[i].url;
            a.appendChild(document.createTextNode(data[i].name));
            li = document.createElement('li');
            li.appendChild(a);
            appendToElement.appendChild(li);
        }    
    }
    //最明显的方法:
    var ul = document.gerElementById('mylist');
    appendDataToElement(ul, data);
    
    //但是这种方法,data的每一个新条目被附加到当前DOM树时都会导致重排。
    //第一种方法,改变display属性,临时从文档中移除<ul>元素,然后再回复它:
    var ul = document.getElementById('mylist');
    ul.style.display = 'none';
    appendDataToElement(ul, data);
    ul.style.display = 'block';

              ②使用文档片段在当前DOM之外构建一个子树,再把它拷贝回文档(推荐使用)

    var fragment = document.createDocumentFragment();
    appendDataToElement(fragment,data);
    docuemnt.getElementById(mylist').appendChild(fragment);

              ③将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素

    var old = document.getElementById('mylist');
    var clone = old.cloneNode(true);
    appendDataToElement(old,data);
    old.parentNode.replaceChild(clone,old);

    缓存布局信息

        myElement.style.left = 1 + myElement.offsetLeft + 'px';
      --->  current++;  myElement.style.left = 1 + current + 'px';

    让元素脱离动画流

      一般情况,重排只影响渲染树中的一小部分,但也可能影响很大的部分,甚至整个渲染树。浏览器所需要重排的次数越少,应用程序的响应速度就越快。

      因此当页面的一个动画推移页面整个余下的部分时,会导致一次代价昂贵的大规模重排,用户会感到页面一顿一顿的。渲染树中需要重新计算的节点越多,情况就会越糟。

      拒绝重排:1、使用绝对位置定位页面上的动画元素,将其脱离文档流。

           2、让元素动起来。当它扩大时,会临时覆盖部分页面,但这只是页面一个小区域的重绘过程,不会产生重排并重绘页面的大部分内容。

           3、当动画结束时恢复定位,从而只会下移一次文档的其他元素。

    IE和:hover

      从IE7开始,IE允许在任何元素(严格模式)上使用 :hover 这个css伪选择器。但是如果大量使用 :hover,那么会降低响应速度。这个问题在IE8中更为明显。

      很大的表格或很长的列表,应避免使用这种效果。

    事件委托

     当页面中存在大量元素,而且每一个都要一次或者多次绑定事件处理器时,这种情况可能会影响性能。每绑定一个事件处理器都是有代价的 。需要访问和修改的DOM元素越多,应用程序也就越慢,特别是时间绑定通常发生在onload(或DOMContentReady)时,此时对每一个富交互应用的网页来说都是一个拥堵的时刻。事件绑定占用了处理的时间,而且,浏览器需要跟踪每个事件处理器,这也会占用更多的内存。当这些工作结束时,这些事件处理器中的绝大部分都不再需要(因为并不是100%的按钮或链接会被用户点击),因此有很多工作是没有必要的。

    事件委托可以很好的处理这类问题。原理:事件逐层冒泡并能被父级元素捕获。使用事件代理,只需要给外层元素绑定一个处理器,就可以处理在其子元素上触发的所有事件。

      1、访问事件对象,并判断事件源

      2、取消文档树中的冒泡(可选)

      3、阻止默认动作(可选)

    小结:

    最小化DOM访问次数,尽可能在JavaScript端处理。

    如果需要多次访问某个DOM节点,请使用局部变量存储它的引用

    小心处理HTML集合,因为它实时连系着底层文档,把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中。

    如果可能的话,使用速度更快的API,比如querySelectorAll()和firstElementChild。

    要留意重绘和重排;批量修改样式时,“离线”操作DOM树,使用缓存,并减少访问布局信息的次数

    动画中使用绝对定位,使用拖放代理

    使用事件委托来减少事件处理器的数量

  • 相关阅读:
    从BATS交易所获取空头头寸
    用cython提升python的性能
    用Python编写的第一个回测程序
    Omi框架学习之旅
    Omi框架学习之旅
    AlloyTouch.js 源码 学习笔记及原理说明
    AlloyFinger.js 源码 学习笔记及原理说明
    Git 学习笔记
    从数组中每次取一个不同的数组成员 getRandomItem(arr)
    move.js 源码 学习笔记
  • 原文地址:https://www.cnblogs.com/liuyanan/p/10980933.html
Copyright © 2011-2022 走看看