浏览器页面渲染流程

浏览器页面渲染流程

渲染流程

浏览器渲染流程是什么?

渲染的过程其实就是将url对应的各种资源, 通过浏览器渲染引擎的解析,输出可视化的图像:

HTML/CSS/JavaScript => 浏览器渲染引擎 => 图像
  1. 浏览器解析HTML文件为DOM

    当我们打开一个网页,浏览器请求对应的HTML(在网络传输中是0和1的字节数据),将这些字节数据转换为字符串(我们写的代码).

    接着再将字符串通过词法分析转换为标记(token),这一过程在词法分析中称为标记化(tokenization).

    那么什么是标记呢?这其实属于编译原理这一块的内容了。简单来说,标记还是字符串,是构成代码的最小单位。这一过程会将代码分拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思
    

    结束标记后,这些标记会紧接着被转换为Node,最后根据这些Node之间的联系,构建DOM树.

    字节数据 => 字符串 => token => Node => DOM树
    
  2. CSS文件转换为CSSOM树(CSS对象模型树)

    这一过程与构建DOM树是相似的.

    字节数据 => 字符串 => token => Node => CSSOM树
    

    在这一个过程中,浏览器会确认每一个节点的样式是什么,并且这个过程是很消耗资源的(样式的设置是多样化的).因此,我们应该尽可能的避免写过于具体的CSS选择器,如div > a > span,然后对于HTML来说,也尽量少的添加无意义标签,保证层级扁平.

    根据页面渲染流程可得知:

    • css加载不会阻塞DOM树的解析,但会阻塞DOM树的渲染;
    • css加载会阻塞后面js语句的执行
  3. 生成Render Tree(渲染树)

    生成DOM树和CSSOM树后,就会将这两颗树组合为渲染树.

    这一过程并不是简单的合并,`render tree`只会包括需要显示的节点和这些节点的样式信息,比如,如果某个节点的样式是`display:none`,就不会在`render tree`中显示.
    
  4. 浏览器生成render tree后,就会根据render tree来进行布局(也叫回流或者重排),然后调用GPU绘制,合成图层,显示在屏幕上.

阻塞渲染

什么情况会阻塞渲染?怎么解决?

阻塞 解决
渲染的前提首先是生成渲染树,因此HTMLCSS的解析肯定会阻塞渲染 应该从一开始降低需要渲染的文件大小,比如HTML保证层级扁平,CSS优化选择器
浏览器解析到script标签时,会暂停DOM的构建.解析完js后才会从暂停的地方重新开始构建 不应该在首屏加载js文件,将script标签至于body底部(当然,也可以添加deferasync属性)
defer属性表示该js文件会并行下载,但是会放到HTML解析完成后执行.
对于没有任何依赖的js文件可以添加async属性,表示js文件的下载和解析不会阻塞渲染.

重绘&回流

  • 重绘(repaint): 当渲染树中的元素外观(如:color)发生改变,不影响布局时,产生重绘;
  • 回流(reflow): 当渲染树中的元素布局(如:尺寸,位置,隐藏状态)发生改变时,产生回流(重排);
  • JS获取Layout的属性值(如:offsetLeft,scrollTop,getComputedStyle等),也会引起回流,因为浏览器需要通过回流重新计算最新的值;
  • 回流必将引起重绘,而重绘不一定会引起回流;

如何针对重绘和回流进行前端优化?

  1. 需要对元素进行复杂的操作时,可以先隐藏 该元素(display:none),操作完成以后,再显示;
  2. 需要创建多个DOM节点时,使用DocumentFragment创建完后一次性地加入document;
  3. 缓存Layout的属性值,如:let left = elem.offsetLeft,这样多次使用left只产生第一次的回流;
  4. 尽量避免使用table布局,table元素一旦触发回流就会导致table里所有的其他元素回流;
  5. 尽量避免css表达式(expression),因为每次调用都会重新计算值(包括加载页面);
  6. 尽量使用css属性的简写,如用border代替border-width,border-style,border-color;
  7. JS中批量修改元素的样式,如:elem.classNameelem.style.cssText代替elem.style.xxx;

DOM操作

操作DOM性能为什么会变差?

  1. DOM属于渲染引擎,JS属于JS引擎,通过JS操作DOM涉及了两个线程之间的通信,势必会带来一些性能的损耗.操作DOM的次数一多,就等同于一直在进行线程之间的通信.
  2. 操作DOM可能会带来重绘和回流的情况.

经典面试题:插入几万个DOM,怎么实现页面不卡顿?

首先,不可能把几万个DOM一次性插入,这样做是绝对会卡顿的,解决问题的关键应该从减少DOM操作次数缩短循环时间两个方面去减少主线程阻塞的时间.

  1. DocumentFragment

    减少DOM操作次数的良方是createDocumentFragment API,它用来创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点.DocumentFragment节点不属于文档树,继承的parentNode属性总是null.

    DocumentFragment有一个很实用的特点,当请求把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment自身,而是它的所有子孙节点.这使得它起到了一个暂存节点的作用.因此,当需要添加多个dom元素时,如果先将这些元素添加到DocumentFragment中,再统一将DocumentFragment添加到DOM树种的节点,可以减少页面渲染DOM的次数,效率明显提升.

    以下是原生插入3万个节点和利用DocumentFragment插入3万个节点的对比:

    // 原生插入
    console.time('原生插入耗时')
    
    const list = document.getElementById('ul');
    let insertCount = 30000; // 插入的节点数
    let count = 0; // 当前已插入的节点数
    function protoRender() {
         
      while (count <= insertCount) {
         
        const li = document.createElement('li');
        li.innerHTML = `原生插入节点${
           count}`;
        list.appendChild(li);
        count++;
      }
    }
    protoRender();
    
    console.timeEnd('原生插入耗时'); // 原生插入耗时: 154.080322265625 ms
    
    // DocumentFragment 插入
    consol
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值