• 160826、浏览器渲染页面过程描述,DOM编程技巧以及重排和重绘


    一、浏览器渲染页过程描述

     

    1、浏览器解析html源码,然后创建一个DOM树。

    在DOM树中,每一个HTML标签都有一个对应的节点(元素节点),并且每一个文本也都有一个对应的节点(文本节点)。

    DOM树的根节点就是documentElement,对应的是html标签。

    2、浏览器解析CSS代码,计算出最终的样式数据。

    对CSS代码中非法的语法它会直接忽略掉。

    解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置,用户设置,外联样式,内联样式,html中的style(嵌在标签中的行间样式)。

    3、创建完DOM树并得到最终的样式数据之后,构建一个渲染树。

    渲染树和DOM树有点像,但是有区别。DOM树完全和html标签一一对应,而渲染树会忽略不需要渲染的元素(head、display:none的元素)。

    渲染树中每一个节点都存储着对应的CSS属性。

    4、当渲染树创建完成之后,浏览器就可以根据渲染树直接把页面绘制到屏幕上。

    二、高性能Javascript DOM编程

     

    我们知道,用脚本进行DOM操作的代价很昂贵。把DOM和ECMAScript各自想象为一个岛屿,它们之间用收费桥梁链接,ECMAScript每次访问DOM,都要经过这座桥,并交纳“过桥费”,访问DOM的次数越多,费用也就越高。因此,推荐的做法是尽量减少过桥的次数,努力呆在ECMAScript岛上。这是比喻非常形象。那么怎样才能高效呢?

    1、DOM访问与修改

    访问DOM元素是有代价的(这里其实和ajax调后台数据接口是一样,DOM是用于操作XML和HTML文档的应用程序接口,一次能完成的事不要请求多次),通过DOM获取元素之后,修改元素的代价更是昂贵,因为它会导致浏览器重新计算页面的几何变化(重排和重绘)。

    例子:

    var times = 15000;

    //code1

    console.time('time1');

    for(var i=0; i<times; i++){

      document.getElementById('myDiv1').innerHTML +='a';

    }

    console.timeEnd('time1');

     

    //code2

    console.time('time2');

    var str = '';

    for(var i=0; i<times; i++){

      str += 'a';

    }

    document.getElementById('maDiv2').innerHTML = str;

    console.timeEnd('time2');

    结果time1:2352.064ms/time2:0.789ms

    第一段代码的问题在于,每次循环迭代,改元素都会被访问两次:一次读取innerHTML的值,另一次重写它。

    结果充分证明,访问DOM的次数越多,代码的运行速度越慢。

    因此,能减少DOM访问的次数就尽量减少,尽量留在ECMAScript这端处理。

    2、html集合&遍历DOM

    操作DOM另一个耗能点就是遍历DOM,在平时获取一组元素的时候(getElementsByTagName方法),收集的结果是一个类数组对象,它处于一种“实时状态”实时存在,这意味着当底层文档对象更新时,它也会自动更新。

    例子:

    而这正是低效之源!很简单,跟数组的优化操作一样,缓存一个length变量就OK了(读取一个集合的length比读取一个普通数组的length要慢很多,因为每次都要查询):

    console.time('time0');

    var lis0 = document.getElementsByTagName('li');

    var str0 = '';

    for(var i=0; i<lis0.length; i++){

      str0 +=lis0[i].innerHTML;

    }

    console.timeEnd('time0');

    console.time('time1');

     

    var lis1 = document.getElementsByTagName('li');

    var str1 = '';

    for(var i=0; len=lis1.length; i<len; i++){

      str1 += lis1[i].innerHTML;

    }

    console.timeEnd('time1');

    结果:time0:0.237ms/time1:0.099ms

    当获取的这个类数组对象长度值特别大的时候(如1000),性能提升还是很明显的。

    第一部分主要提了两点优化,一是尽量减少DOM的访问,把运算放在ECMAScript这一端,二是尽量缓存局部变量,比如类数组对象的length。

    三、重排和重绘

    1、什么是重排和重绘

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

    DOM树表示页面结构,渲染树表示DOM节点如何显示。DOM树中的每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素 display:none 在渲染树中没有对应的节点)。

    渲染树中的节点被称为‘帧’或‘盒’,符合CSS模型的定义,页面元素为一个具有填充,边距,边框和位置的盒子。一旦DOM树和渲染树构建完成,浏览器就开始绘制页面元素。

    当DOM的变化影响了元素的几何属性(宽或高),浏览器需要重新计算元素的几何属性,同时其他元素的几何属性和位置也会受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树,这个过程称为重排。完成重排后,浏览器会重新绘制受到影响的部分到屏幕,这个过程称为重绘。

    由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。table及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花三倍同等元素的时间。这也是为什么我们要避免使用table做布局的一个原因。

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

    2、重排何时发生

    a、添加或删除可见的DOM元素

    b、元素位置改变

    c、元素尺寸改变

    d、元素内容改变

    e、页面渲染初始化

    f、浏览器窗口尺寸改变

    3、渲染树变化的排队和刷新

    获取布局信息的操作会导致队列刷新,比如:

    a、offsetTop,offsetLeft,offsetWidth,offsetHeight

    b、scrollTop,scrollLeft,scrollWidth,scrollHeight

    c、clientTop,clientLeft,clientWidth,clientHeight

    d、getComputeStyle()||currentStyle(IE)

    因为offsetHeight属性需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并触发重排以返回正确的值(即使队列中 改变的样式属性和想要获取的属性值并没有什么关系)。

    4、最小化重排和重绘

    改变元素多种样式的时候,最好用className,一次性完成操作,这样只会修改一次DOM。

    5、文档碎片

    文档碎片的设计初衷就是为完成这类任务——更新和移动节点。当你附加一个片段到节点时,实际上被添加的是该片段的子节点,而不是片段本身。只会触发一次重排,而且只访问一次实时的DOM。

    用法:

    var oFragment = document.createDocumentFragment();

    for(var i=0; i<1000; i++){

      var oP = document.createElement('p');

      oP.innerHTML = i;

      oFragment.appendChild(oP);

    }

    var oDo = document.body;

    oDo.appendChild(oFragment);

    6、让动画脱离文档流

    使用绝对位置定位页面上的动画元素,将其脱离文档流。这样不会导致文档流中的元素受到影响,不会大规模的进行重排和重绘。

    第二部分重排和重绘是DOM编程中耗能的主要原因之一,为了避免不必要的性能损耗可以参考一下几点:

    1. 尽量不要在布局信息改变时做查询(会导致渲染队列强制刷新);

    2. 同一个DOM的多个属性改变可以写在一起(减少DOM访问,同时把强制渲染队列刷新的风险降为0);

    3. 如果要批量添加DOM,可以先让元素脱离文档流,操作完成后带人文档流,这样只会触发一次重排(fragment元素的应用);

    4. 将需要多次重排的元素,添加定位属性,设置为absolute,fixed,这样此元素就脱离了文档流,不会影响其他元素。

  • 相关阅读:
    web服务器-Apache
    nginx优化
    nginx下载限速
    nginx-URL重写
    HDU 5358 First One 求和(序列求和,优化)
    HDU 5360 Hiking 登山 (优先队列,排序)
    HDU 5353 Average 糖果分配(模拟,图)
    UVALive 4128 Steam Roller 蒸汽式压路机(最短路,变形) WA中。。。。。
    HDU 5348 MZL's endless loop 给边定向(欧拉回路,最大流)
    HDU 5344 MZL's xor (水题)
  • 原文地址:https://www.cnblogs.com/zrbfree/p/5818627.html
Copyright © 2020-2023  润新知