浏览器渲染原理

952 阅读5分钟

浏览器接受到HTML文件并转换为DOM树

当我们打开一个网页时,浏览器会去请求对应的HTML文件。虽然我们平时写代码都会分为JS、CSS、HTML文件,就是字符串,但是浏览器硬件是不理解这些字符串的,所以网络传输的内容其实都是01这些字节数据。当浏览器接受到这些字节数据以后,它将会这些字节数据转换为字符串。也就是我们写的代码。
当数据转换为字符串后,浏览器会先将这些字符串通过词法分析转化为标记(token),这一过程在词法分析中做标记化(tokenization)。
字节数据 ==> 字符串 ==> Token
什么是标记呢 ?这其实数据编译原理这一块的内容了。简单来说,标记还是字符串,是构成代码的最小单位。这一过程会将代码拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思。 image.png 当结束标记化后,这些标记会紧接着转换为Node,最后这些Node会根据不同的Node之前的联系构成一个DOM树。 image.png

将CSS文件转换为CSSOM树

其实转换CSS到CSSOM树的过程和上面的过程及其相似。
字节数据 ==> 字符串 ==> Token ==> Node ==> CSSOM
在这一过程中,浏览器会确定下一个节点的样式到底是什么,并且在这一过程其实是很消耗资源的。因为样式你可以自行设置给某一个节点,也可以通过继承获得。在这一过程中,浏览器的递归CSSOM树,然后确定具体的元素到底是什么样式。

生成渲染树

当我们生成DOM树和CSSOM树后,就需要将这两棵树组合成渲染树。 image.png 在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是display: none的,那么就不会在渲染树中显示。
当浏览器生成渲染树后,会根据渲染树来进行布局(也可以叫做回流),然后调用GPU绘制,合成图层,显示在屏幕上。

为什么操作DOM慢

大家开发过程中都听过操作DOM性能差,但是这其中的原因是什么呢 ?
因为DOM是属于渲染引擎中的东西,而JS又是JS引擎中的东西。当我们遇到JS操作DOM的时候,其实这个操作设计到了两个线程之间的通信,那么势必会带来一些性能上的消耗。操作DOM次数一多,也就等同于一直在进行线程之间的通信,并且操作DOM可能还会带来重绘回流的情况,所以也就导致了性能上的问题

什么情况阻塞渲染

首先渲染的前提是生成渲染树,所以HTML和CSS肯定会阻塞渲染。如果我们想渲染的越快,就应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器
当浏览器解析到script标签时,会暂停构建DOM,完成后才会从暂停的地方重新开始。也就是说,如果想首屏渲染的越快,就越不应该在首屏就加载JS文件,这就是建议将script标签放在body标签底部的原因。
当然我们可以利用给script标签添加defer或者async属性的方式,来解决JS阻塞加载的问题。
defer表示该JS文件会并行下载,但是会放在HTML解析完成后顺序执行,此时我们可以吧script标签放在任意位置。
async表示JS文件下载不会阻塞渲染,但解析会阻塞。

image.png

重绘(Repaint)和回流(REflow)

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

  • 重绘是当节点需要改外观而不影响布局的,比如改变color就叫称为重绘
  • 回流是布局或者集合属性需要改变就称为回流。
    回流必定触发重绘,重绘不一定会引发回流。回流所需要的成本比重绘高很多,改变父节点里的节点和可能会导致父节点的一系列回流。
    以下几个动作可能会导致性能问题:
  • 改变window大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型

并且重绘和回流其实也和Eventloop有关。

  1. 当Eventloop执行完Microtasks后,会判断document是否需要更新,因为浏览器是60Hz的刷新率,每16.6ms才会更新一次。
  2. 然后判断是否有resize或者scroll事件,有的话才会去触发事件,所以resizescroll事件也是至少16ms才会触发一次,并且自带节流功能。
  3. 判断是否触发media query
  4. 更新动画并且发送事件
  5. 执行requestAnimationFrame回调
  6. 执行InstersectionObserver回调,该方法判断元素是否可见,可以用于懒加载上,但是兼容性不好。
  7. 更新界面
  8. 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行requestIdCallback回调。

减少重绘和回流

  • 使用transform替代top
  • 使用visibility替代display: none,前者只会引起重绘,后者会引发回流(改变了布局)。但是前者仍在文档流中占据空间,只是视觉效果上的不可见。
  • 不要把节点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 10; i++) {
    // 获取offsetTop会导致回流,因为需要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop);
}
  • 不要使用table布局,可能很小的一个改动会造成整个table的重新布局。
  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用requestAnimationFrame
  • CSS选择符是从右往左匹配查找,避免节点层级过多
  • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的行为影响别的节点。比如video,浏览器会自动将该节点变为图层。