解锁浏览器背后的运行机制
浏览器的"心"
之所以不同的浏览器下代码渲染结果有差异 正是因为浏览器的内核不一致导致的 , 浏览器的内核决定了浏览器解释网页语法的方式.
浏览器内核可以分为两部分 渲染引擎 和 js引擎
渲染引擎包含了 HTML解释器 , css 解释器 , 布局 , 网络 , 存储 , 图形 , 音视频 , 图片解码器等零件
开启浏览器渲染"黑盒"
Q : 什么是渲染过程 ? A : 简单的说 渲染引擎根据 html 文件描述 将资源转换为图像结果
其中比较重要的部分是
- HTML 解释器 : 将 html 文档经过词法分析输出 DOM 树
- CSS 解释器 : 解析 css 文档 , 生成样式规则
- 图层布局计算模块 : 布局计算每个对象的精确位置和大小
- 视图绘制模块 : 进行具体的图像绘制 , 将像素渲染到屏幕上
- javascript 引擎 : 编译 执行 js 代码
浏览器渲染过程解析
-
解析 HTML 在这一步浏览器执行了所有的加载解析逻辑,在解析 HTML 的过程中发出了页面渲染所需的各种外部资源请求。
-
计算样式 浏览器将识别并加载所有的 CSS 样式信息与 DOM 树合并,最终生成页面 render 树(:after :before 这样的伪元素会在这个环节被构建到 DOM 树中)。
-
计算图层布局 页面中所有元素的相对位置信息,大小等信息均在这一步得到计算。
-
绘制图层 在这一步中浏览器会根据我们的 DOM 代码结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。
-
整合图层,得到页面 最后一步浏览器会合并合各个图层,将数据由 CPU 输出给 GPU 最终绘制在屏幕上。(复杂的视图层会给这个阶段的 GPU 计算带来一些压力,在实际应用中为了优化动画性能,我们有时会手动区分不同的图层)。
几棵重要的"树"
-
DOM 树: 解析 HTML 以创建的是 DOM 树(DOM tree ):渲染引擎开始解析 HTML 文档,转换树中的标签到 DOM 节点,它被称为"内容树"。
-
CSSOM 树: 解析 CSS(包括外部 CSS 文件和样式元素)创建的是 CSSOM 树。CSSOM 的解析过程与 DOM 的解析过程是并行的。
-
渲染树: CSSOM 与 DOM 结合,之后我们得到的就是渲染树(Render tree )。
-
布局渲染树: 从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树(Layout of the render tree)。
-
绘制渲染树: 遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树(Painting the render tree)。
渲染过程: 首先是基于 HTML 构建一个 DOM 树,这棵 DOM 树与 CSS 解释器解析出的 CSSOM 相结合,就有了布局渲染树。最后浏览器以布局渲染树为蓝本,去计算布局并绘制图像
之后每当一个新元素加入到这个 DOM 树当中,浏览器便会通过 CSS 引擎查遍 CSS 样式表,找到符合该元素的样式规则应用到这个元素上,然后再重新去绘制它。
css 优化建议
css
#myList li {
/* */
}
CSS 选择符是从右到左进行匹配的。这个选择器实际开销相当高:浏览器必须遍历页面上每个 li 元素,并且每次都要去确认这个 li 元素的父元素 id 是不是 myList
通配符也是个消耗性能的选择器 , 因为他会遍历页面上的每一个元素
告别阻塞: css 与 js 的加载顺序优化
html css js 都具有阻塞渲染的特性
css 阻塞
因为 DOM Tree + CSSOM = Render Tree 所以只要 css 没加载完 就不会往后走渲染流程
由于 html 解析到 <link /> 和 <style />
标签的时候 CSSOM 才开始构建 . 所以可以尽可能把构建时机提前.
比如 :
- 放在
<head />
里 (尽早) - 启用 CDN (尽快)
js 阻塞
js 的作用在于 修改节点
, 本质上都是对 DOM 和 CSSOM 进行修改 , 所以 JS 的执行会阻塞
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>JS阻塞测试</title>
<style>
#container {
background-color: yellow;
width: 100px;
height: 100px;
}
</style>
<script>
// 尝试获取container元素
var container = document.getElementById("container");
console.log("container", container);
</script>
</head>
<body>
<div id="container"></div>
<script>
// 尝试获取container元素
var container = document.getElementById("container");
console.log("container", container);
// 输出container元素此刻的背景色
console.log(
"container bgColor",
getComputedStyle(container).backgroundColor
);
</script>
<style>
#container {
background-color: blue;
}
</style>
</body>
</html>
运行结果是
javscript
container null
index.html:26 container <div id="container"></div>
index.html:28 container bgColor rgb(255, 255, 0)
index.html:63 Live reload enabled.
JS 是外部文件的时候 表现也是这样的
第一次获取 DOM 失败 说明 js 阻塞了 DOMTree 的生成 第二次获取 DOM 成功 (预期内) 但是获取 css 拿到的是 yellow 的颜色 而不是后续配置的 blue 证明 CSSOM 也被阻塞了
js 的加载方式
- 正常模式
这种模式下会阻塞
javascript
<script src="./index.js" />
- async 模式
async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。
javascript
<script async src="./index.js" />
- defer 模式
defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。
javascript
<script defer src="./index.js" />