本文不是去剖析浏览器的底层原理或者具体的前端优化细节,而主要是讲述浏览器是如何对页面进行渲染的,即浏览器的渲染原理。
前端相关的技术,日新月异,更新迭代的速度非常快,只有掌握了渲染原理,才能更好的设计优化方案,因为本来前端的各种预编译、预加载、按需加载、资源合并等优化方案,都是针对浏览器的渲染来进行优化的,最终的目的就是呈现好的效果给用户,给用户一个好的使用体验。
Chrome
说到浏览器的页面渲染,有几个不得不提到的相关概念。
关键渲染路径:是指与当前用户的操作有关的内容。说白了就是浏览器呈现给用户的页面,具体内在的浏览器接收的 HTML / CSS / JavaScript 之类的可以不管先,知道是用户所看到的页面即可。
渐进式渲染:是指用尽可能快的速度呈现给用户页面内容的技术。举个简单的例子就很容易理解这句话了,jpeg文件有两种保存方式,分别是标准型与渐进式,标准型是从上到下的扫描,内容相当于是一行一行的展现出来的(网速慢的时候可以看出很明显的效果),而渐进式是多次扫描,先显示图片轮廓,接着每次扫描的精度逐渐提高,呈现的内容越来越清晰,直至加载完成。
Key
渲染过程我们从几个大方面看,在地址栏输入地址后,看看都干了些什么:
- DNS服务器解析URL中域名所对应的IP
- 解析IP,与服务器建立TCP连接
- 浏览器发起HTTP请求/响应
- 服务器响应所请求的内容
- 客户端即浏览器对内容进行渲染
这里我想说说的是第5部分的内容,也就是浏览器是如何对内容进行渲染的。而这一过程,又可以更加具体的细分:
- 解析 HTML,构建 DOM Tree
- 处理 CSS,构建 CSSOM Tree
- 根据 DOM 树和 CSSOM 树生成渲染树
- 根据渲染树来布局和绘制
- 渲染树发生变化时的回流和重绘
如上的几个具体细节,并不是一次性按顺序完成的,而是不断重复的过程,解析 - 构建 - 布局 - 绘制 - 重绘,不断的计算哪些内容需要渲染,进而在屏幕上呈现。
Paint
阻塞资源DOM(Document Object Model)树的构建过程,是一个深度遍历的过程,只有在当前节点的所有子节点都构建完成后,当前节点才会继续构建下一个兄弟节点。另外,DOM Tree 的根节点就是 Document 。
CSS 被视为渲染阻塞资源,即在 CSSOM Tree 构建完成前,浏览器不会对任何已经处理的内容进行渲染。JavaScript 不仅可以读取和修改 DOM 属性,同时还可以读取和修改 CSSOM 属性,所以存在阻塞的 CSS 资源时,JavaScript 会被延迟执行,DOM 构建也将延迟。
JavaScript 被视为解释器阻塞资源,HTML 的解析会被 JavaScript 阻塞。另外需要注意的是 document.createElement 创建的 script 标签时 async 的属性值。
综合上述两点,很显然说的是 HTML 解析器在构建 DOM 过程时:
当遇到 script 标签时,DOM 构建延迟,直至 JavaScript 脚本执行完成。
CSSOM 构建时,JavaScript 脚本执行将延迟,直至 CSSOM 构建完成。
知道了上述两点,也就很容易的能总结出前端项目优化的一些规则:
CSS 优先,先于 JavaScript 资源
JavaScript 尽量少影响 DOM 构建
JavaScript
阻塞模式script 标签有两个属性,分别为 defer 和 async,即延迟加载脚本和异步加载脚本,能改变上述所说的阻塞情况,但这两个属性对于内联脚本标签无效,针对的是带了 src 属性的 script 标签,如下所示:
// 从上直下顺序执行,defer / async 属性无效
<script defer>
console.log('https://blog.makeit.vip');
</script>
<script async>
console.log('https://account.makeit.vip')
</script>
defer 属性,表示的是延迟加载执行,按序执行,即在 HTML 解析过程中,不会阻塞 DOM 构建,直至解析完成后,按顺序进行加载执行。
// DOM 构建完成,然后按顺序加载 JavaScript 脚本
<script src="makeit-blog.js" defer></script>
<script src="makeit-account.js" defer></script>
async 属性,表示的是异步加载执行,JavaScript 脚本加载期间,不影响 HTML 解析,但是一旦加载完成,将暂停 DOM 构建,进而先执行 JavaScript 脚本内容,即使 DOM 构建完成,处于 DOMContentLoaded 状态,异步脚本加载完成后就立马执行,但一定要在 Load 状态之前执行。执行并无先后顺序,谁先加载完成就先执行谁。
上面说到 document.createElement 创建 script 标签,需要注意的就是 async 属性默认是为 true 的,若是不希望如此的话,就需要手动修改该属性值了。
总结就先记录这么多吧,主要记住以下几点:
- CSS 资源阻塞渲染,没构建完成前,浏览器是不会渲染任何内容的。
- JS 阻塞 HTML 解析
- CSS 资源优先加载
- defer 与 async,延迟与异步加载脚本