美文网首页
前端性能优化--DOM篇

前端性能优化--DOM篇

作者: TingsLee | 来源:发表于2018-02-05 16:16 被阅读0次

    前言

    DOM(Document Object Model)是一个与语言无关的、用来操作XMl和HTML文档的应用程序接口。在浏览器环境下,我们是通过Javascript来实现对DOM的操作。而针对DOM进行频繁操作会严重影响前端的体验和性能,下面我们就简单谈一下如何针对这种情况进行一些优化。

    浏览器工作原理

    先通过一张图来看一下浏览器是如何进行页面画渲染的:

    浏览器工作流程

    首先通过HTML文件生成DOM Tree和CSS规则生成CSS Rule Tree,然后Javascript可以通过DOM API和CSSOM API对生成后的DOM Tree和CSS Rule Tree进行需要的操作。接下来CSS Rule Tree中的规则应用到DOM Tree上,构建出Rendering Tree。最后调用操作系统Native GUI的API绘制。

    当Rendering Tree中的元素尺寸大小,布局位置,显示隐藏等改变时,需要对Rending Tree进行重新构建,这个过程就称为回流。回流完成后浏览器会重新绘制受影响的部分。回流必引起重绘,而重绘不一定引起回流。

    天生就慢的DOM如何优化

    实际上Javascipt的执行速度虽然比不上C、C++等预编译语言,但是和访问、操作DOM�比起来那还是要快很多的。当你的Rendering Tree足够大时,每次使用Javascript调用DOM API去访问一个DOM元素,都会执行一个遍历过程,而当你又对获取的元素进行了操作,这就又会触发回流和重绘,这些都会大量消耗性能,很容易造成页面闪烁,卡顿等,影响用户体验。所以我们沿着这个思路就能找到要优化的点。

    1. 使用尽可能少的DOM元素来构建页面

    页面中过多的DOM元素,会使定位元素变得缓慢。当触发回流和重绘操作时会消耗更多的性能。还有就是在后期的维护上也会增加不必要的复杂性。如果页面真的很复杂,精简DOM不可行,可以用局部变量缓存获取过的DOM元素:

    function cacheDOM() {
      // 缓存DOM节点,避免在循环中不断重新获取
      let div = document.getElementsByTagName('div'), 
      let len = div.length;
      for(let i = 0; i < len; i++) {
        console.log(div.nodeType);
      }  
    }
    

    还可以使用一些高效的API,优先使用document.getElementById,而document.querySelectordocument.querySelectorAll在性能上和getElementsBy*API相比较慢。

    2. 减少回流和重绘

    在操作DOM元素时,除了获取过程,还有对元素的外观,布局等进行的修改。触发回流的操作有以下类型:

    1. 添加或者删除可见的DOM元素

    2. 改变元素的位置

    3. 元素的内边距、外边距、边框、宽度、高度等尺寸的改变

    4. 元素文本内容和图片大小的改变导致高度和宽度的重新计算

    5. 浏览器窗口尺寸的改变

    6. 页面初始化渲染

       var s = document.body.style;
       s.padding = "2px"; // 回流+重绘
       s.border = "1px solid red"; // 再一次 回流+重绘
       s.color = "blue"; // 再一次重绘
       s.backgroundColor = "#ccc"; // 再一次 重绘
       s.fontSize = "14px"; // 再一次 回流+重绘
       // 添加node,再一次 回流+重绘
       document.body.appendChild(document.createTextNode('abc!'));
      

    通过上面的代码可以看出回流和重绘频繁发生。

    而在实际的浏览器渲染过程中,会对上述过程做优化。连续的回流和重绘会被放入一个队列中,当任务积累到一定数量或者一定的时间间隔,就会刷新队列。减少了多次渲染的性能消耗。但是有些操作,浏览器为了返回正确的值,会忽略优化,立刻执行回流和重绘操作,比如:offsetTopscorllTopclientTopwidth等。
    如何减少DOM的回流和重绘呢?我们可以从以下几个方面入手:

    a. 在动态修改CSS样式时,不要逐条设置,可以使用类名

        element.style.fontWeight = 'bold' ;
        element.style.marginLeft= '30px' ;
        element.style.marginRight = '30px' ;
    
        // 可以优化为如下代码,可以将多次回流减少到一次
        element.className = 'selectedAnchor' ;
    

    b. 对DOM的操作,留在Javascript内部,然后一次写回DOM,即实现离线处理

    第一种,可以使用DocumentFragment的机制:DocumentFragment接口表示没有父级的最小文档对象,由于文档片段不是实际DOM结构的一部分,它是一个虚拟的dom节点,存在于内存中,所以对片段所做的更改不会影响文档,导致回流,影响性能。

      function CreateFragments(){
        var fragment = document.createDocumentFragment();
        for(var i = 0;i < 10000;i++){
          var tmpNode = document.createElement("div");
          tmpNode.innerHTML = "test" + i + "<br />";
          fragment.appendChild(tmpNode);
        }
        document.body.appendChild(fragment);
      }
    

    第二种,使用使用cloneNode在外部更新节点然后再通过replace与原始节点互换,也只触发一次回流重绘。

      var orig = document.getElementById('container');
      var clone = orig.cloneNode(true);
      var list = ['foo', 'bar', 'baz'];
      var content;
      for (var i = 0; i < list.length; i++) {
        content = document.createTextNode(list[i]);
        clone.appendChild(content);
      }
      orig.parentNode.replaceChild(clone, orig);
    

    还有一种方法,比较简单,将要操作的DOM设置为dispaly:none,操作完成后再设置为display:block,这样只触发了两次回流。

    c. 对于动画元素,尽可能使其脱离文档流,使用fixedabsolute定位。在进行动画制作时,如果能用opcity和transform实现,就不要使用其他属性。这两个属性只会触发composite,而不会触发回流。

    总结

    本文从浏览器的工作原理触发,简单说明了一下DOM慢的原因,然后给出了优化DOM操作的几个建议。主要目的就是为了精简DOM结构、减少DOM的回流和重绘。如果要对页面的性能瓶颈进行详细的分析,可以借助Chrome开发工具中的Performance面板。找到具体的原因,针对性的提出优化方案。

    相关文章

      网友评论

          本文标题:前端性能优化--DOM篇

          本文链接:https://www.haomeiwen.com/subject/tlkizxtx.html