浏览器渲染机制

概述

浏览器渲染依靠其渲染引擎,渲染引擎通过解析 HTML 和 CSS 构建渲染树,然后调用 GPU 接口在屏幕上呈现出图像。

一、常见浏览器渲染引擎一览

按照时间排序展示:

  • Gecko:前身来自伊利诺伊大学的NCSA,由Netscape(网景) 开源,Netscape浏览器、Mozilla Firefox 浏览器等使用
  • Trident(也称MSHTML):微软公司创建,IE浏览器
  • KHTML:由开源志愿者开发的一个开源引擎,Linux 平台开始使用,Konqueror 浏览器使用
  • Webkit:基于 KHTML 引擎,Apple 公司创建,Safari 浏览器以及其他其他一些 Mac OS X 程序在使用,Chrome 浏览器最初也在使用
  • Blink:基于 Webkit 引擎,谷歌公司创建,Chrome 浏览器使用
  • Chakra(也称 EdgeHTML ):微软公司创建,Edge 浏览器和其他 UWP 浏览器使用

二、常见渲染引擎工作原理:

2.1 常见渲染引擎逻辑图

如图所示:

图2.1.1 WebKit引擎

图2.1.2 Gecko引擎

对于这两张图,我会对以下的几个方面略过:

  • 解析 CSS、 HTML 的不讲,这部分是将代码编译成特定的格式,属于编译器的知识;
  • 如何合并 CSS、HTML 构建 Render(Frame) Tree 也不讲,这也是个大工程;
  • 还有最后的Display 。Display 就是展示,指将 Render(Frame) Tree 转换成像素的过程,也就是屏幕展现出的效果,我们眼睛能看见的东西。这涉及到 GPU 层面的知识,略过。

这里只讲图的2个部分,分别是:

  • Render Tree,事实上,Frame Tree 和这个概念没什么不同,只不过这是火狐的说法
  • Layout,Reflow 同上,也是和Layout相同的概念

2.2 Render Tree(Frame Tree)

中文解释是渲染树,它描述了各个 DOM 元素的层级、位置关系(##注意此处,描述的是位置结构的东西!!##),是其逻辑实现。
你要是了解 Vue 或者 React 的虚拟 DOM,就应该懂 Render Tree 的意思,它也是这么一个东西,不过它是真实 DOM 面向 GUI 的映射(用来帮助渲染的),而虚拟 DOM 是组件面向真实 DOM 的映射。

2.3 Layout(Reflow)

在谷歌、IE、Safari等等浏览器的叫法叫做 Layout(布局),火狐叫做 Reflow(回流),这是一种行为,一种构建和调整 Render Tree 的行为。你可以想象成在 Vue 和 React 中,对虚拟 DOM 的diff 以及调整。说到这,我看了很多人的说法都是采用Webkit的逻辑图,说着回流的概念,其实应该说布局的。

JS 操作一个DOM,如果涉及到 Layout(Reflow),比如元素删除、调整宽度、调整行高等等会影响布局的操作(具体有哪些点击这里),会马上终止 JS,触发 Layout,这种情况叫做强制同步布局,这是一个时间花费长的过程。在后文的性能章节,我会对此更多阐述。

三、渲染性能

在后面的阐述中,我会以 Webkit 渲染引擎为例子,后续的相关术语以及概念也是以 Webkit 为参考,其他引擎都大差不差。

3.1 Layout 相关

3.1.1 避免 JS 连续触发 Layout

JS 涉及到 Layout 的操作时,会触发 Layout,此时 JS 任务挂起,Layout 完毕再继续 JS 操作。如果大量的这种 操作连续出现,会造成页面卡顿(因为 Layout 很费时,导致 Render Tree 得不到及时的更新)。

解决方式是采用类似CPU时间分片的方式,将一系列的操作分解成一个个合适的操作(合适的意思是说,该操作的花费总合小于一个固定的时间),在某段时间只执行一个,通用的是采用 setTimeout 实现。 不过 setTimeout 有一个弊端,了解 JS 的知道,JS 是单线程,JS 运行时间超过 setTimeout 的时间,则 setTimeout 完成后得不到及时的处理,这样导致 Render Tree 照样得不到及时更新,照样卡顿。

说到这,我们得了解 GUI 在浏览器的工作方式,通常 GUI 在 1 秒内的绘制次数和屏幕刷新率一致,通常是60帧。有个浏览器的新接口 window.requestAnimationFrame 解决了 setTimeout 的弊端,这个接口会在每一帧绘制前调用,确保了时间的一致性。

3.1.2 避免操作大量DOM

Layout 的速度取决于处理 DOM 的多寡,因为一个 DOM 会产生蝴蝶效应,会影响很多 DOM 的布局。
解决方式:

  • 尽可能减少会涉及很多 DOM 的操作
  • 多使用 Transition、Opacity,这些有相关的 GUI 优化
  • 多层次的布局(比如脱离了文档流的float,absolute,全局的z-index等等),会在其内容范围内执行 Layout,所以有选择的布局也是优化之一

3.2 编译器相关

减少HTML、CSS文档不必要的换行,也就是说压缩后的 HTML、CSS 执行的速度快,这是在词法解析的层面加速

3.3 文件加载优化

在谷歌浏览器中,如果你的 css 代码在 body 后,则解析成 DOM Tree 后便会开始初步渲染,直接显示在屏幕上。这意味着,CSS 加载完成后将会再次渲染,这是对引擎的浪费,所以通常的优化手段是CSS放在body前引入。

还有就是 JS 和渲染引擎在同一个主线程上运行,所以它们是互斥的。设置 JS 文件加载为异步或者在最后加载,尽可能让 JS 代码在 HTML 元素 Body 之后执行,不然页面渲染会卡顿。

四、其他

4.1 各个渲染引擎的异同

相同的是,我们得知道 渲染、JS 使用的同一个主线程,一方执行,另一方就得停下,关于这个主线程的描述,在 MDN谷歌开发者web文档 都有详尽的说明,确实是同一个线程且互斥的,这里我不多加阐述。

在我个人的测试中,不同渲染引擎的执行和JS的执行有点点不同:

  • 在 WebKit 引擎中,构建 Render Tree 后不会立即渲染,而是执行 js 代码
  • 而火狐浏览器则是会先渲染,再执行之后的js代码

下面的代码是我的例子,大家可以尝试:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    p {
        color: red;
    }
</style>

<body>
    <p>1</p>
</body>
<script>
    alert("中断渲染线程!") //交互事件会阻碍主线程的执行,js和渲染都会停下工作
</script>

</html>

我在 chrome 浏览器和 safari 浏览器中的测得的效果一致:有弹窗,但是屏幕上什么都没出现
在火狐浏览器中测试的效果:有弹窗,屏幕上是理想的效果,出现了红色的数字 1 。

个人比较赞同火狐的做法,先渲染再执行js代码,这是比较人性化的,用户希望看见的是效果而不是关心js代码。
猜测 WebKit 引擎可能是为了性能考量,毕竟渲染费时费力,先运行js代码,将第一遍运行js产生的修效果直接合并,再尔进行渲染。

原文地址:https://www.cnblogs.com/panshaojun/p/14976728.html