大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

大量前端技术可用于修改资源加载的优先级,有些已经存在了很长一段时间,例如: <script> 标签上的 async 和 defer 属性,而有些则相对较新。
例如:<link rel="preload"> 于 2016 年左右才推出,其允许开发者告诉浏览器某个特定资源肯定会在页面上使用,因此应该以更高优先级下载。目前,该策略已经在各种网站上用于资源的预加载。
<head> <!-- 其他内容 --> <link rel="preload" href="./script.js" as="script"> <script src="./script.js"></script></head>以上 <link> 标签的 as 属性标记了正在预加载的内容类型,这对于请求匹配、应用正确的内容安全策略 (CSP) 以及设置正确的 Accept 请求标头至关重要。
不可否认的是,如果某个文件对于页面体验至关重要,则确实应该提前告诉浏览器优先处理。然而在大多数时候,该策略不一定与预期相符。
1. HTML 文档中资源默认具有高优先级并预加载下面是一个简单的 HTML 文档,在 <head> 中加载部分样式资源,在 <body> 中加载了部分图像和脚本。
<head> <link rel="stylesheet" href="./style.css"> <link rel="stylesheet" href="./style2.css"> <link rel="stylesheet" href="./style3.css"></head><body> <img src="./g/1201/1201" /> <img src="./g/1202/1202" /> <img src="./g/1203/1203" /> <script src="./script.js"></script></body>第一个 CSS 文件,即 style.css,还通过 @font-face 规则加载了 .woff2 字体文件。但显然,该字体文件只有在浏览器解析之后才会去加载。
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; // 特定的字体文件 src: url(./font.woff2) format('woff2');}从下面瀑布流可以看出,在页面加载时,大部分资源按照文档位置的顺序加载(瀑布流上面的资源都先加载)。

由于直接嵌入在文档本身中,因此大多数资源都以 “Medium” 到 “Highest” 优先级加载。而字体文件有点特殊,部分浏览器会以 “Medium” 优先级加载,而 Chrome 则以 “Highest” 优先级加载。但由于在测试时 Chrome 模拟的是慢速 3G 网络连接,因此浏览器会自动降低该资源优先级并转而使用默认字体。
@font-face { font-family: ExampleFont; src: url(/path/to/fonts/example-font.woff) format("woff"), url(/path/to/fonts/example-font.eot) format("eot"); font-weight: 400; font-style: normal; font-display: fallback; // 给字体极小的阻塞周期和较短的交换周期}然而最有趣的是,尽管优先级被标记为 “Medium”(Network 里面),但 JavaScript 在所有图像之前就开始加载。
2. 预加载确实可以提升资源在瀑布流中的优先级接下来需要重新审视预加载页面上已调用的真正重要的 <script> 脚本的想法,首先在顶部添加新代码。
<head>+ <link rel="preload" href="./script.js" as="script"> // 新增预加载代码 <link rel="stylesheet" href="./style.css"> <link rel="stylesheet" href="./style2.css"> <link rel="stylesheet" href="./style3.css"></head><body> <img src="./g/1201/1201" /> <img src="./g/1202/1202" /> <img src="./g/1203/1203" /> <script src="./script.js"></script></body>此时,网络瀑布图如下所示:

从表面上看,脚本确实已成为第一个开始下载的资源,但时间上微乎其微。虽然从屏幕截图中看不出来,但开始下载的时间确实比最后一张图片早了 16 毫秒。而且从图上可知,资源(子请求的字体除外)仍在并行加载。
同时注意,脚本已被升级为 “High” 优先级,不过与预期的 “Highest” 优先级还有差距,而样式表仍然排名最高。无论样式表放在文档中的任何位置,浏览器似乎已经知道如何根据不同资源的位置和类型来处理优先级。
预加载资源的优先级分配可以阅读我的其他专栏或文章3. 预加载扫描器(Preload Scanner)足够替代手动预加载从上文可知,如果想从 rel="preload" 中获取巨大获益,那可能会大失所望。因为浏览器已经非常擅长上面手动所做的预加载操作,称为 预加载扫描器 机制。
预加载扫描器是在任何文档加载时运行的辅助解析器,其目的是在文档中向前查看,识别 HTML 中可以尽快开始获取的资源,包括: <img>、<link>、<script> 等。
这意味着无论 <script> 标记放在何处,浏览器都会预先找到并在加载时为其赋予合理的优先级,并与其他资产相关联。当然,这种行为也有一些前提:
没有使用 async 或 defer 属性 (会降低优先级)标签在 HTML 文档中而 非通过 JavaScript 注入HTML 不是由 JavaScript 框架(如:React)渲染,即 CSR<link rel="stylesheet" href="styles.css" /><script src="my-script.js" async></script><img src="my-image.jpg" alt="image description" /><script src="another-script.js" async></script>在上面示例中,当主线程解析 HTML 和 CSS 时,预加载扫描器将找到脚本和图像并开始下载。因此,为确保脚本不会阻塞进程,开发者可以添加 async 或者 defer 属性。
总之,浏览器比想象的要聪明,但这并不意味着预加载真的是一个鸡肋。
4. 如何让预加载扫描器成为浏览器导盲犬与预加载已嵌入 HTML 中的资源相比,预加载真正的用武之地是使用其来优先处理稍后会需要但尚未在 HTML 文档中出现的资源。换句话说,预加载扫描器可以被用作浏览器内置预加载扫描仪的导盲犬,例如:低优先级字体文件。
尽管 Chrome 足够智能,但可能开发者确实希望强制执行更高的优先级而不管网络速度如何。比如下面的示例:
<head>+ <link rel="preload" href="./font.woff2" as="font"> <link rel="stylesheet" href="./style.css"> <link rel="stylesheet" href="./style2.css"> <link rel="stylesheet" href="./style3.css"></head><body> <img src="./g/1201/1201" /> <img src="./g/1202/1202" /> <img src="./g/1203/1203" /> <script src="./script.js"></script></body>此时,瀑布流大不相同。

如上图所示,字体文件已成为第一个要获取的资源,并且具有 “High” 优先级,在实际需要之前就可以提前下载。当 “最低” 优先级的字体请求到达时,文件已经缓存并准备就绪,这就是预加载扫描器真正发挥威力的地方。
5. 如何最大化预加载扫描器的作用上文的结论难道是:预加载 HTML 中已经存在的任何内容实际上没有价值?相反,应该可以得出结论:如果止步于预加载 HTML 嵌入的资源,那么就错过了预加载扫描器真正的威力。
考虑到这一点,可以遵循下面的最佳实践:
密切关注会自行获取更多资源的资源:例如,Google Fonts 要求开发者将 CSS 文件放到页面上,而页面转而请求字体文件。预加载扫描器无法提前看到这些文件,而确实值得预加载。单页应用 JavaScript: 在 JavaScript 下载、解析和执行之前都无法获取任何内容。 在这些情况下,预加载扫描器完全看不到关键的静态资源,此时声明性地预加载可以带来巨大的收益。如果运行了 Lighthouse 报告并看到了预加载图像的建议,可能会看到下面的免责声明:

如果 LCP 元素是动态添加到页面的,预加载非常有用。
其次,需要注意预加载扫描器无法捕获的内容,特别是元素上的内联背景图像:
<div style="background-url: url('https://whatever/image.jpg');"> Some content.</div>此类资源由 CSS 解析器处理,因此不在预加载扫描器的范围之内。即使被硬编码到服务器渲染的 HTML 中,开发者也最好手动预加载这些图像。
最后,不要过度使用手动预加载,因为对某些资源来说确实不会带来太多好处,反而会损害性能。记住下面一句话:
如果有太多资源争用带宽,则在启动期间预加载过多的 JavaScript 可能会带来意想不到的负面影响。参考资料https://macarthur.me/posts/effective-preloading/
https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload
https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc/edit?ref=cms.macarthur.me&tab=t.0
https://calendar.perfplanet.com/2022/http-3-prioritization-demystified/?ref=cms.macarthur.me
https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
https://web.dev/articles/preload-critical-assets?ref=cms.macarthur.me