前面也说过,当在浏览器上输入一个 url,到页面加载出来,简单来说一共经历了这么几件事:
DNS 查询
其实就是将一个域名,解析成 ip 地址的一个过程,它会先去找缓存(比如说浏览器缓存 -> 本机缓存 (hosts 文件)-> 路由器缓存)。找不到缓存的话,会跑去 DNS 服务器进行一个查询(具体怎么搞就不展开了),找到域名对应的 ip 地址之后,就进行第二步。
TCP 连接
对服务端的 ip 地址建立一个 tcp 连接,主要就是三次握手的过程。
HTTP 请求
发送一些请求来请求资源,最常见的就是请求 html 文档以及对应的图片、css 等资源了。这个过程还涉及到一些浏览器缓存(会先看本地有无缓存、强缓存协商缓存之类的,以前的博客也有,不详细展开)
服务器响应
浏览器渲染
最后还有个释放 TCP 连接
这篇笔记就是用于整理我从找到的各位大牛的知识文章加以整理得来的。
# 解析 HTML 文档,构建 DOM 树
当浏览器接收到一份文档的时候,它会去将这个文档的 HTML 标记进行一个处理、解析,从而将其转换为一颗 DOM 树。
# 获取外部资源,构建 CSSOM 树
一份文档里面通常还会去下载一些外部的样式表、或者外部的 js 代码,这个阶段之下,有一些需要注意的点。
- 当浏览器遇到一个 script 标签时,会暂停构建 DOM 直到脚本执行完毕
- JavaScript 可以查询和修改 DOM 与 CSSOM。
- CSSOM 构建时,会暂停执行 JavaScript 直到 CSSOM 构建完毕。
总结来说,总而言之两句话:相比 JavaScript 资源,优先引入 CSS。而且 JavaScript 尽可能少的影响 DOM 的构建。
# CSS 文件
** 默认情况下,CSS 会阻塞渲染,直到 CSSOM 树构建完毕。** 这是因为 CSSOM 树不能以增量方式构建,css 规则的特定性让他可以在各个不同的点相互覆盖,在所有规则解析、并构建完之前,浏览器不能知道每个元素的位置。
CSSOM(CSS 对象模型)是树形形式的所有 css 选择器和相关属性的映射,和 DOM 非常相似。
也可以进行预加载,
<link href='style.css' rel='preload'/> |
preload 属性值表示这份资源在页面加载完即可需要,它会在页面加载的生命周期早期阶段就开始获取,尽早的加载,减少阻塞。
# JavaScript 文件
刚刚也提到,JavaScript 文件会阻塞文档的解析直到执行完毕,所以我们一般都会将 js 文件放在底部。
当然,也有两种方法来改变这种阻塞的情况(只对外部 js 文件有效),分别是:defer 和 async
# defer
defer 表示延迟执行引入的 JS 文件代码(注意,是延迟执行,不是延迟加载)。这使得加载这段 JS 文件时浏览器也未停止对 HTML 的解析,并行。当文档解析完毕后,按照顺序执行所有 defer 的脚本。
# async
async 表示异步执行引入的 JS 文件。它和 defer 的区别主要是,async 如果加载完毕就立即执行,这个执行的发生可能在解析过程或者解析过程之后。而且因为加载时间的不一,也无法保证异步脚本的执行顺序。
他们两种设置了之后都不会阻塞文档的解析。
需要注意的是,js 代码动态创建的 script 标签默认是异步加载的。
# 合并 DOM 树和 CSSOM 树,构建渲染树
渲染树是两棵树的结合,表示将要渲染到页面上的所有内容。
# reflow、paint
拿到渲染树之后,浏览器将会根据这一棵渲染树,来计算布局和绘制,这个过程就是 reflow(重排 / 回流)和 paint(绘制 / 重绘)。
一般来说,每次涉及到元素宽度、高度等会影响到文档布局的修改,都会触发 reflow 和 paint。
如果只是涉及到元素背景色、透明度等不影响文档布局的修改,就只会触发 paint。
# 总结
浏览器渲染的过程主要有以下几点:
- 处理 HTML 标签并构建 DOM 树
- 处理 CSS 样式并构建 CSSOM 树
- 将 DOM 与 CSSOM 合并成渲染树
- 根据渲染树计算布局,reflow
- 绘制,paint。
这几个步骤中,并不一定是按照顺序一次性完成的,在 JS 文件中可能涉及多次对 DOM 和 CSSOM 的修改,就可能会重复执行以上的步骤。
而且 js 文件会阻塞文档解析,css 文件会阻塞文档渲染(因为需要构建 CSSOM 树)。
# 扩展:为什么提倡将 CSS 的引入放在 head、js 的引入放在 body 底部?
前面我们也说到,css 文件会阻塞浏览器的渲染,而 js 文件会阻塞对文档的解析。
之所以会阻塞渲染,是因为加载 css 的时候,会等解析完毕生成 CSSOM 树才能够生成渲染树。
js 文件加载、执行的过程中,这个时间一般是比较长的,而且可能文件本身比较大、一旦长期执行,就会一直阻塞 HTML 文档的解析,使得页面长时间白屏(性能指标的首屏时间),所以我们一般把 js 文件放在 body 底部。
而 CSS 文件放的位置不同,产生的效果也不同。
如果把 CSS 文件放于 head 部分
虽然在 head 标签引入时,会阻塞 HTML 的渲染,这个时候表现为短暂的白屏之后,出现带有完整样式的页面。这个过程只发生一次解析渲染:
DOM 树、CSSOM 树并行解析 -> 解析完毕合成渲染树 ->reflow->paint
如果把 CSS 文件放于 body 部分,他会阻塞 HTML 的解析,但不阻塞 HTML 的渲染(在 css 完成下载之前把引入标签之前的 HTML 先进行解析渲染,并展现,但没有样式),然后下载解析完成之后,页面重新解析渲染。这个过程表现为:更加短暂的白屏时间,但先出现没有样式的文档(或者说裸奔),然后再展现完整样式。
而这个过程会导致在后面重新解析渲染的过程中,多一次的 reflow 和 paint,一般来说性能会相对较差,所以我们多把 CSS 引入放于 head 处。