• 重绘与重排及它的性能优化


    1.重绘与重排

    浏览器下载完页面中的所有组件——HTML标记、JavaScript、CSS、图片之后会解析生成两个内部数据结构——DOM树和渲染树。

    DOM树表示页面结构,渲染树表示DOM节点如何显示。DOM树中的每一个需要显示的节点在渲染树种至少存在一个对应的节点(隐藏的DOM元素disply值为none 在渲染树中没有对应的节点)。渲染树中的节点被称为“帧”或“盒”,符合CSS模型的定义,理解页面元素为一个具有填充,边距,边框和位置的盒子。一旦DOM和渲染树构建完成,浏览器就开始显示(绘制)页面元素。

    当DOM的变化影响了元素的几何属性(宽或高),浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为重排。完成重排后,浏览器会重新绘制受影响的部分到屏幕,该过程称为重绘。由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。但table及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。这也是为什么我们要避免使用table做布局的一个原因。

    并不是所有的DOM变化都会影响几何属性,比如改变一个元素的背景色并不会影响元素的宽和高,这种情况下只会发生重绘。

    不管页面发生了重绘还是重排,它们都会影响性能(重绘还好一些) 
    能避免要尽量避免

     

    2触发重排

    页面布局和元素几何属性的改变就会导致重排 
    下列情况会发生重排

    • 页面初始渲染
    • 添加/删除可见DOM元素
    • 改变元素位置
    • 改变元素尺寸(宽、高、内外边距、边框等)
    • 改变元素内容(文本或图片等)
    • 改变窗口尺寸

    不同的条件下发生重排的范围及程度会不同 
    某些情况甚至会重排整个页面,比如滑动滚动条

     

    3.浏览器的优化:渲染队列

    举个小例子 
    比如我们想用js中修改一个div元素的样式 
    写下了以下代码

    div.style.left = '10px';

    div.style.top = '10px';

    div.style.width = '20px';

    div.style.height = '20px';

    我们修改了元素的left、top、width、height属性 
    满足我们发生重排的条件 
    理论上会发生4次重排 
    但是实际上只会发生1次重排 
    这是因为我们现代的浏览器都有渲染队列的机制 
    当我改变了元素的一个样式会导致浏览器发生重排或重绘时 
    它会进入一个渲染队列 
    然后浏览器继续往下看,如果下面还有样式修改 
    那么同样入队 
    直到下面没有样式修改 
    浏览器会按照渲染队列批量执行来优化重排过程,一并修改样式 
    这样就把本该4次的重排优化为1次

    但是我们现在想要修改样式后在控制台打印

    div.style.left = '10px';

    console.log(div.offsetLeft);

    div.style.top = '10px';

    console.log(div.offsetTop);

    div.style.width = '20px';

    console.log(div.offsetWidth);

    div.style.height = '20px';

    console.log(div.offsetHeight);

    千万不要写这样的代码,因为发生了4次重排 
    有同学可能不懂了,不是说浏览器有渲染队列优化机制吗? 
    为什么这样写就会发生4次重排 
    因为offsetLeft/Top/Width/Height非常叼 
    它们会强制刷新队列要求样式修改任务立刻执行 
    想一想其实这么做是有道理的 
    毕竟浏览器不确定在接下来的代码中你是否还会修改同样的样式 
    为了保证获得正确的值,它不得不立刻执行渲染队列触发重排(错的不是我,是这个世界)

    以下属性或方法会刷新渲染队列

    • offsetTop、offsetLeft、offsetWidth、offsetHeight
    • clientTop、clientLeft、clientWidth、clientHeight
    • scrollTop、scrollLeft、scrollWidth、scrollHeight
    • getComputedStyle()(IE中currentStyle)

    我们在修改样式过程中,要尽量避免使用上面的属性

     

    4重绘与重排的性能优化

    A 分离读写操作

    了解了原理我们就可以对上面的代码进行优化

    div.style.left = '10px';

    div.style.top = '10px';

    div.style.width = '20px';

    div.style.height = '20px';

    console.log(div.offsetLeft);

    console.log(div.offsetTop);

    console.log(div.offsetWidth);

    console.log(div.offsetHeight);

    这样就仅仅发生1次重排了,原因相信大家已经很清晰了 
    把所有的读操作移到所有写操作之后 
    效率高多了 
    这是其中一种优化的方法

     

    B 样式集中改变

    还是我们最初修改样式的代码

    div.style.left = '10px';

    div.style.top = '10px';

    div.style.width = '20px';

    div.style.height = '20px';

    虽然现代浏览器有渲染队列的优化机制 
    但是古董浏览器效率仍然底下,触发了4次重排 
    即便这样,我们仍然可以做出优化 
    我们需要cssText属性合并所有样式改变

    div.style.cssText = 'left:10px;top:10px;20px;height:20px;';

    这样只需要修改DOM一次一并处理 
    仅仅触发了1次重排 
    而且只用了一行代码,看起来相对干净一些

    不过有一点要注意,cssText会覆盖已有的行间样式 
    如果想保留原有行间样式,这样做

    div.style.cssText += ';left:10px;';

     

    除了cssText以外,我们还可以通过修改class类名来进行样式修改

    div.className = 'new-class';

    这种办法可维护性好,还可以帮助我们免除显示性代码 
    (有一点点性能影响,改变class需要检查级联样式,不过瑕不掩瑜)

     

    C 缓存布局信息

    我觉得缓存真是万金油,哪种性能优化都少不了它

    div.style.left = div.offsetLeft + 1 + 'px';

    div.style.top = div.offsetTop + 1 + 'px';

    这种读操作完就执行写操作造成了2次重排 
    缓存可以进行优化

    var curLeft = div.offsetLeft;

    var curTop = div.offsetTop;

    div.style.left = curLeft + 1 + 'px';

    div.style.top = curTop + 1 + 'px';

    这也相当于是分离读写操作了 
    优化为1次重排

     

    D元素批量修改

    现在我们想要向ul中循环添加大量li 
    (如果ul还不存在,最好的办法是先循环添加li到ul,然后再把ul添加到文档,1次重排)

    var ul = document.getElementById('demo');

    for(var i = 0; i < 1e5; i++){

        var li = document.createElement('li');

        var text = document.createTextNode(i);

        li.appendChild(text);

        ul.appendChild(li);

    }

    我可以做出下面的优化

    var ul = document.getElementById('demo');

    ul.style.display = 'none'; <--

    for(var i = 0; i < 1e5; i++){

        var li = document.createElement('li');

        var text = document.createTextNode(i);

        li.appendChild(text);

        ul.appendChild(li);

    }

    ul.style.display = 'block'; <--

    var ul = document.getElementById('demo');

    var frg = document.createDocumentFragment(); <--

    for(var i = 0; i < 1e5; i++){

        var li = document.createElement('li');

        var text = document.createTextNode(i);

        li.appendChild(text);

        frg.appendChild(li); <--

    }

    ul.appendChild(frg); <--

    var ul = document.getElementById('demo');

    var clone = ul.cloneNode(true); <--

    for(var i = 0; i < 1e5; i++){

        var li = document.createElement('li');

        var text = document.createTextNode(i);

        li.appendChild(text);

        clone.appendChild(li); <--

    }

    ul.parentNode.replaceChild(clone,ul); <--

    上面的方法减少重绘和重排的原理很简单

    • 元素脱离文档
    • 改变样式
    • 元素回归文档

    而改变元素就分别使用了隐藏元素、文档碎片和克隆元素 
    上面的方法我认为仅仅是理论上可以优化重排重绘次数 
    现代浏览器的优化可能会超过我们的想象

     

     

     

  • 相关阅读:
    C# struct 性能损失
    [leetcode] Scramble String @python
    [leetcode]Symmetric Tree @ Python
    [leetcode] Maximum Product Subarray @ python
    [leetcode]Surrounded Regions @ Python
    [leetcode]N-Queens @ Python
    [leetcode] Combinations @ Python [ask for help]
    [leetcode]Next Permutation @ Python
    [building block] merge sort @ Python
    [leetcode] Integer to Roman @ Python
  • 原文地址:https://www.cnblogs.com/ranyonsue/p/8328120.html
Copyright © 2020-2023  润新知