浏览器渲染原理

🌙
手机阅读
本文目录结构

问题

浏览器渲染原理

渲染过程

1. 浏览器接收到 HTML ⽂件并转换为 DOM 树

当我们打开⼀个⽹⻚时,浏览器都会去请求对应的 HTML ⽂件。虽然平时我 们写代码时都会分为 JS 、 CSS 、 HTML ⽂件,也就是字符串,但是计算机 硬件是不理解这些字符串的,所以在⽹络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据以后,它会将这些字节数据转换为 字符串,也就是我们写的代码。

当数据转换为字符串以后,浏览器会先将这些字符串通过词法分析转换为标记 ( token ),这⼀过程在词法分析中叫做标记化( tokenization )

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

当结束标记化后,这些标记会紧接着转换为 Node ,最后这些 Node 会根据 不同 Node 之前的联系构建为⼀颗 DOM 树

以上就是浏览器从⽹络中接收到 HTML ⽂件然后⼀系列的转换过程 当然,在解析 HTML ⽂件的时候,浏览器还会遇到 CSS 和 JS ⽂件,这时 候浏览器也会去下载并解析这些⽂件,接下来就让我们先来学习浏览器如何解 析 CSS ⽂件

2. 将 CSS ⽂件转换为 CSSOM 树

其实转换 CSS 到 CSSOM 树的过程和上⼀⼩节的过程是极其类似的

在这⼀过程中,浏览器会确定下每⼀个节点的样式到底是什么,并且这⼀过程其实是很消 耗资源的。因为样式你可以⾃⾏设置给某个节点,也可以通过继承获得。在这⼀过程中, 浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。

如果你有点不理解为什么会消耗资源的话,我这⾥举个例⼦

<div>
    <a> <span></span> </a>
</div>
<style>
    span {
    color: red;
    }
    div > a > span {
    color: red;
    }
</style>

对于第⼀种设置样式的⽅式来说,浏览器只需要找到⻚⾯中所有的 span 标 签然后设置颜⾊,但是对于第⼆种设置样式的⽅式来说,浏览器⾸先需要找到 所有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜⾊,这样的递归过程 就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加⽆意义标签,保证层级扁平

3. ⽣成渲染树

当我们⽣成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树

在这⼀过程中,不是简单的将两者合并就⾏了。渲染树只会包括需要显示的节点和这些节 点的样式信息,如果某个节点是 display: none 的,那么就不会在渲染树中显示。

当浏览器⽣成渲染树以后,就会根据渲染树来进⾏布局(也可以叫做回流),然后调⽤ GPU 绘制,合成图层,显示在屏幕上。对于这⼀部分的内容因为过于底层,还涉及到了硬 件相关的知识,这⾥就不再继续展开内容了。

21.2 为什么操作 DOM 慢

想必⼤家都听过操作 DOM 性能很差,但是这其中的原因是什么呢?

因为 DOM 是属于渲染引擎中的东⻄,⽽ JS ⼜是 JS 引擎中的东⻄。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来⼀些性 能上的损耗。操作 DOM 次数⼀多,也就等同于⼀直在进⾏线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

经典⾯试题:插⼊⼏万个 DOM,如何实现⻚⾯不卡顿?

对于这道题⽬来说,⾸先我们肯定不能⼀次性把⼏万个 DOM 全部插⼊,这样肯定会造成 卡顿,所以解决问题的重点应该是如何分批次部分渲染 DOM 。⼤部分⼈应该可以想到通 过 requestAnimationFrame 的⽅式去循环的插⼊ DOM ,其实还有种⽅式去解决这个问 题:虚拟滚动( virtualized scroller )。

这种技术的原理就是只渲染可视区域内的内容,⾮可⻅区域的那就完全不渲染了,当⽤户 在滚动的时候就实时去替换渲染的内容

从上图中我们可以发现,即使列表很⻓,但是渲染的 DOM 元素永远只有那么 ⼏个,当我们滚动⻚⾯的时候就会实时去更新 DOM ,这个技术就能顺利解决 这道经典⾯试题

21.3 什么情况阻塞渲染

⾸先渲染的前提是⽣成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越 快,你越应该降低⼀开始需要渲染的⽂件⼤⼩,并且扁平层级,优化选择器。

然后当浏览器在解析到 script 标签时,会暂停构建 DOM ,完成后才会从暂停的地⽅重 新开始。也就是说,如果你想⾸屏渲染的越快,就越不应该在⾸屏就加载 JS ⽂件,这也 是都建议将 script 标签放在 body 标签底部的原因。

当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。

当 script 标签加上 defer 属性以后,表示该 JS ⽂件会并⾏下载,但是会放到 HTML 解析完成后顺序执⾏,所以对于这种情况你可以把 script 标签放在任意位置。

对于没有任何依赖的 JS ⽂件可以加上 async 属性,表示 JS ⽂件下载和解析不会阻 塞渲染。

21.4 重绘(Repaint)和回流(Reflow)

重绘和回流会在我们设置节点样式时频繁出现,同时也会很⼤程度上影响性 能。

  • 重绘是当节点需要更改外观⽽不会影响布局的,⽐如改变 color 就叫称为重绘
  • 回流是布局或者⼏何属性需要改变就称为回流。
  • 回流必定会发⽣重绘,重绘不⼀定会引发回流。回流所需的成本⽐重绘⾼的多,改变⽗节点⾥的⼦节点很可能会导致⽗节点的⼀系列回流。

以下⼏个动作可能会导致性能问题:

  • 改变 window ⼤⼩
  • 改变字体
  • 添加或删除样式
  • ⽂字改变
  • 定位或者浮动
  • 盒模型

并且很多⼈不知道的是,重绘和回流其实也和 Eventloop 有关。

  • 当 Eventloop 执⾏完 Microtasks 后,会判断 document 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新⼀次。
  • 然后判断是否有 resize 或者 scroll 事件,有的话会去触发事件,所以 resize 和scroll 事件也是⾄少 16ms 才会触发⼀次,并且⾃带节流功能。
  • 判断是否触发了 media query
  • 更新动画并且发送事件
  • 判断是否有全屏操作事件
  • 执⾏ requestAnimationFrame 回调
  • 执⾏ IntersectionObserver 回调,该⽅法⽤于判断元素是否可⻅,可以⽤于懒加载上,但是兼容性不好 更新界⾯
  • 以上就是⼀帧中可能会做的事情。如果在⼀帧中有空闲时间,就会去执⾏requestIdleCallback 回调

21.5 减少重绘和回流

  1. 使⽤ transform 替代 top
<div class="test"></div>
<style>
    .test {
        position: absolute;
        top: 10px;
        width: 100px;
        height: 100px;
        background: red;
    }
</style>
<script>
    setTimeout(() => {
    // 引起回流
    document.querySelector('.test').style.top = '100px'
    }, 1000)
</script>
  1. 使⽤ visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流 (改变了布局)

  2. 不要把节点的属性值放在⼀个循环⾥当成循环⾥的变量

for(let i = 0; i < 1000; i++) {
    // 获取 offsetTop 会导致回流,因为需要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop)
}
  1. 不要使⽤ table 布局,可能很⼩的⼀个⼩改动会造成整个 table 的重新布局

  2. 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使⽤requestAnimationFrame

  3. CSS 选择符从右往左匹配查找,避免节点层级过多

  4. 将频繁重绘或者回流的节点设置为图层,图层能够阻⽌该节点的渲染⾏为影响别的节点。⽐如对于 video 标签来说,浏览器会⾃动将该节点变为图层。

设置节点为图层的⽅式有很多,我们可以通过以下⼏个常⽤属性可以⽣成新图层

will-change

video 、 iframe 标签

更多面试题

如果你想了解更多的前端面试题,可以查看本站的WEB前端面试题 ,这里基本包涵了市场上的所有前端方面的面试题,也有一些大公司的面试图,可以让你面试更加顺利。

面试题
HTML CSS JavaScript
jQuery Vue.js React
算法 HTTP Babel
BootStrap Electron Gulp
Node.js 前端经验相关 前端综合
Webpack 微信小程序 -

这些题库还在更新中,如果你有不错的面试题库欢迎分享给我,我整理后放上来;人人为我,我为人人,互帮互助,共同提高,祝大家都拿到心仪的Offer!

AXIHE / 精选资源

浏览全部教程

面试题

学习网站

前端培训
自己甄别

前端书籍

关于朱安邦

我叫 朱安邦,阿西河的站长,在杭州。

以前是一名平面设计师,后来开始接接触前端开发,主要研究前端技术中的JS方向。

业余时间我喜欢分享和交流自己的技术,欢迎大家关注我的 Bilibili

关注我: Github / 知乎

于2021年离开前端领域,目前重心放在研究区块链上面了

我叫朱安邦,阿西河的站长

目前在杭州从事区块链周边的开发工作,机械专业,以前从事平面设计工作。

2014年底脱产在老家自学6个月的前端技术,自学期间几乎从未出过家门,最终找到了满意的前端工作。更多>

于2021年离开前端领域,目前从事区块链方面工作了