# 资源提示关键词 在上一篇文章中,我们介绍了浏览器的渲染流程,这篇文章中,我们将重点聚焦在渲染阻塞上,来详细看一下渲染阻塞以及一些常见的解决方法。 本文主要包含以下内容: - 渲染阻塞回顾 - *defer* 和 *async* - *preload* - *prefetch* - *prerender* - *preconnect* ## 渲染阻塞回顾 我们都知道,*HTML* 用于描述网页的整体结构。为了理解 *HTML*,浏览器必须将它转为自己能够理解的格式,也就是 *DOM*(文档对象模型) 浏览器引擎有一段特殊的代码,称为解析器,用于将数据从一种格式转换为另一种格式。 image-20211206161457653 浏览器一点一点地构建 *DOM*。一旦第一块代码进来,它就会开始解析 *HTML*,将节点添加到树结构中。 ![ezgif-2-2688553063](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-06-081522.gif) 构建出来的 *DOM* 对象,实际上有 *2* 个作用: - *HTML* 文档的结构以对象的方式体现出来,形成我们常说的 *DOM* 树 - 作为外界的接口供外界使用,例如 *JavaScript*。当我们调用诸如 *document.getElementById* 的方法时,返回的元素是一个 *DOM* 节点。每个 *DOM* 节点都有许多可以用来访问和更改它的函数,用户看到的内容也会相应地发生变化。 ![ezgif-2-01a1ded8c4](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-06-081639.gif) *CSS* 样式会被映射为 *CSSOM*( *CSS* 对象模型),它和 *DOM* 很相似,但是针对的是 *CSS* 而不是 *HTML*。 在构建 *CSSOM* 的时候,无法进行增量构建(不像构建 *DOM* 一样,解析到一个 *DOM* 节点就扔到 *DOM* 树结构里面),因为 *CSS* 规则是可以相互覆盖的,浏览器引擎需要经过复杂的计算才能弄清楚 *CSS* 代码如何应用于 *DOM*。 ![image-20211206161700033](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-06-081700.png) 当浏览器正在构建 *DOM* 时,如果它遇到 *HTML* 中的 `` 标记,它必须立即执行它。如果脚本是外部的,则必须先下载脚本。 过去,为了执行脚本,必须暂停解析。解析会在 *JavaScript* 引擎执行完脚本中的代码后再次启动。 image-20211206161717368 为什么解析必须停止呢? 原因很简单,这是因为 *Javascript* 脚本可以改变 *HTML* 以及根据 *HTML* 生成的 *DOM* 树结构。例如,脚本可以通过使用 *document.createElement( )* 来添加节点从而更改 *DOM* 结构。 ![image](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-06-081740.gif) 这也是为什么我们建议将 *script* 标签写在 *body* 元素结束标签前面的原因。 ```html ``` *defer* 表示加载后续文档元素的过程将和 *script.js* 的加载并行进行(异步),但是 *script.js* 的执行要在所有元素解析完成之后,*DOMContentLoaded* 事件触发之前完成。也就是说,下载 *JS* 文件的时候不会阻塞 *DOM* 树的构建,然后等待 *DOM* 树构建完毕后再执行此 *JS* 文件。 ```html ``` 具体加载瀑布图如下图所示: image-20211208112125053 ## *preload* *preload* 顾名思义就是一种预加载的方式,它通过声明向浏览器声明一个需要提前加载的资源,当资源真正被使用的时候立即执行,就无需等待网络的消耗。 ```html ``` 在上面的代码中,会先加载 *style1.css* 和 *main1.js* 文件(但不会生效),在随后的页面渲染中,一旦需要使用它们,它们就会立即可用。 可以使用 *as* 来指定将要预加载的内容类型。 ![image-20211208112151152](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-08-032151.png) *preload* 指令的一些优点如下: - 允许浏览器设置资源优先级,从而允许 *Web* 开发人员优化某些资源的交付。 - 使浏览器能够确定资源类型,因此它可以判断将来是否可以重用相同的资源。 - 浏览器可以通过引用 *as* 属性中定义的内容来确定请求是否符合内容安全策略。 - 浏览器可以根据资源类型发送合适的 *Accept* 头(例如:*image/webp* ) ## *prefetch* *prefetch* 是一种利用浏览器的空闲时间加载页面将来可能用到的资源的一种机制,通常可以用于加载非首页的其他页面所需要的资源,以便加快后续页面的首屏速度。 *prefetch* 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少 *5* 分钟(无论资源是否可以缓存)。并且,当页面跳转时,未完成的 *prefetch* 请求不会被中断; 它的用法跟 *preload* 是一样的: ```html ``` ***DNS prefetching*** *DNS prefetching* 允许浏览器在用户浏览时在后台对页面执行 *DNS* 查找。这最大限度地减少了延迟,因为一旦用户单击链接就已经进行了 *DNS* 查找。 通过将 *rel="dns-prefetch"* 标记添加到链接属性,可以将 *DNS prefetching* 添加到特定 *URL*。建议在诸如 *Web* 字体、*CDN* 之类的东西上使用它。 ```html ``` ## *prerender* *prerender* 与 *prefetch* 非常相似,*prerender* 同样也是会收集用户接下来可能会用到的资源。 不同之处在于 *prerender* 实际上是在后台渲染整个页面。 ```html ``` ## *preconnect* 我们要讨论的最后一个资源提示是 *preconnect*。 *preconnect* 指令允许浏览器在 *HTTP* 请求实际发送到服务器之前设置早期连接。 我们知道,浏览器要建立一个连接,一般需要经过 *DNS* 查找,*TCP* 三次握手和 *TLS* 协商(如果是 *https* 的话),这些过程都是需要相当的耗时的。所以 *preconnet*,就是一项使浏览器能够预先建立一个连接,等真正需要加载资源的时候就能够直接请求了。 ![image-20211208112216614](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2021-12-08-032217.png) 以下是为 *CDN URL* 启用 *preconnect* 的示例。 ```html ``` 在上面的代码中,浏览器会进行以下步骤: - 解释 *href* 的属性值,判断是否是合法的 *URL*。如果是合法的 *URL*,然后继续判断 *URL* 的协议是否是 *http* 或者 *https*,如果不是合法的 *URL*,则结束处理。 - 如果当前页面 *host* 不同于 *href* 属性中的 *host*,那么将不会带上 *cookie*,如果希望带上 *cookie* 等信息,可以加上 *crossorign* 属性。 ------- -*EOF*-