当你打开浏览器的网络标签时,你会看到大量的活动。资源正在下载,信息正在提交,事件正在记录,等等。
由于有太多的活动,有效地管理这些流量的优先级变得至关重要。带宽争用是真实存在的,当所有请求同时触发时,有些HTTP请求的优先级并不像其他请求那样高。例如,如果你必须选择,你可能更希望某人的付款请求成功完成,而不是仅仅表示他们尝试过的分析请求。而让你的主要图片尽快显示无疑比在页面底部渲染你的标志更为重要。
幸运的是,浏览器拥有越来越多的工具来帮助优先处理所有这些网络活动。这些“优先级提示”帮助浏览器在资源有限时,对哪些请求应该优先处理做出更少的假设和更明确的决策。
这是一套有用的工具,当它们得到很好的利用时,它们可以对页面性能产生实质性的影响,包括那些越来越重要的核心网络指标。让我们探索其中的一些,以及它们最有帮助的一些场景。
这是一套有用的工具,当它们得到很好的利用时,它们可以对页面性能产生实质性的影响,包括那些越来越重要的核心网络指标。让我们探索其中的一些,以及它们最有帮助的一些场景。
优先加载的资源
现代浏览器有一个受到良好支持的方法,可以告诉浏览器当前页面最终需要哪些资源:<link rel="preload" ... />。当它放在文档的<head>中时,浏览器会被指示尽快以“高”优先级下载它。
公平地说,浏览器中的预加载扫描器已经非常擅长这方面的工作。因此,预加载通常最适用于晚些时候发现的资源 - 任何不直接由你的HTML加载的东西,比如通过内联样式属性加载的背景图像。但它也适用于任何其他可能不像你希望的那样被浏览器优先考虑的东西。
例如:默认情况下,Chrome 会以非常高的优先级加载字体,但如果某人的网络连接速度很慢,它会使用备用字体并降低该优先级。
考虑一个仅通过CSS @font-face规则加载的字体:
@font-face {
font-family: "Inter Variable";
src: url("./font.woff2") format("woff2");
}
在加载时,由于网络连接慢,该字体获得了最低的下载优先级,尽管它对于页面的视觉体验非常重要。
但我们可以通过预加载该资源来覆盖浏览器的决定:
<head>
<!-- Other stuff... -->
<link rel="preload" href="/font.woff2" as="font">
</head>
现在它更受欢迎了:
你可以直接在链接标签上使用 fetchpriority 来明确指示相对优先级,这在同时预加载多个资源时非常有用。
这是一个假设的场景,你想预加载两种字体,但想让其中一种优先于另一种:
<link rel="preload" href="./font-1.woff2" as="font" fetchpriority="low" />
<link rel="preload" href="./font-2.woff2" as="font" fetchpriority="high" />
网络活动的结果会反映这些指示。
何时使用
通常,当资源不直接由HTML加载,但对页面的体验至关重要时(例如字体、CSS背景图像等),使用预加载。当预加载多种同类型的资源,且你明确知道哪个最重要时,加入fetchpriority属性。
优先化 fetch() 请求
我认为,Fetch API 是现代网络的最佳工具之一。与 XMLHttpRequest 相比,它有一些很好的功能,比如在外发请求时发出优先信号的能力。
最容易想到的用例是:分析请求。当带宽有限并且有多个请求在执行时,浏览器会自行决定优先级。但我们作为工程师应该知道,通常的分析请求应该优先于页面目的更为关键的其他请求。现代的fetch()使这变得简单。
下面是两个请求几乎同时入队的简单设置:
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
});
默认情况下,浏览器会自动将它们都视为 "高 "优先级:
现在,我们要明确地告诉浏览器每个请求的优先级:
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
+ priority: "high"
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
+ priority: "low"
});
这次,优先级是不同的:
可能的担忧是"low"优先级的请求可能会丢失 - 如果用户过早离开页面,请求可能会被取消。这是一个真正的问题。根据几个因素,关闭标签页或转到下一个页面可能导致一个重要但相对低优先级的请求被中止。
幸运的是,fetch() 还接受一个 keepalive 选项。当设置为true时,即使页面终止,浏览器也会完成该请求。
何时使用
当你知道多个请求正在并发执行,并且你明确知道哪个最重要(或哪个可以安全地被降级)时,指示fetch()的优先级。
优先化<img />请求
如果我们不做任何特殊处理,浏览器会尽量确定页面上最重要的图像。为了说明这一点,我加载了以下图像,它们之间的距离很大,所以只有一个会在"页面首部"显示。
<img src="./cat-1.jpeg" />
<div style="height: 5000px"></div>
<img src="./cat-2.jpeg" />
<div style="height: 5000px"></div>
<img src="./cat-3.jpeg" />
浏览器发现了哪个最重要,但这花了一秒钟。当开始下载时,这三者都是“低”优先级。但很快,页面首部的那个切换到了“高”优先级。
当我为第一张图片添加fetchpriority属性时,情况变得更加可预测:
<img src="./cat-1.jpeg" fetchpriority="high" />
此后,cat-1.jpeg 从一开始就以最高的优先级加载。虽然最初令人费解,但这是有道理的。浏览器非常擅长确定资源的关键性,但它从明确的指示中受益。如果你知道一张图片很重要,就明确说明。
顺便说一句,这个特性与本地图像延迟加载非常搭,这是现在非常受支持的特性。
<img src="./cat-1.jpeg" fetchpriority="high"/>
<div style="height: 5000px"></div>
<img src="./cat-2.jpeg" loading="lazy" />
<div style="height: 5000px"></div>
<img src="./cat-3.jpeg" loading="lazy" />
有了这个,浏览器就知道如何加载图像,只在合适的时候加载。在我的情况下,它甚至不会开始请求初始加载时屏幕外的图像。相反,它会等到它们更接近视口。
何时使用
当你知道它们对页面体验非常重要时,对图像使用明确的fetchpriority。主图像是一个很好的开始,它甚至可以影响页面的核心网络指标 - 特别是LCP(最大内容绘制)。
优先化 <script /> 标签
页面上带有src属性的任何普通<script />在获取时都会得到高优先级,但这有一个权衡:在它加载并执行之前,它会阻止解析页面的其余部分。出于这个原因,async属性很有用。它会以低优先级在后台请求脚本,并在准备好后立即执行。知道这一点,以下设置行为是可预测的:
<script src="/script-async.js" async notallow="console.log('async')"></script>
<script src="/script-sync.js" notallow="console.log('sync')"></script>
<script>console.log("inline");</script>
异步脚本在优先级中被降低:
控制台确认,在 async 脚本加载过程中,允许解析和执行后续脚本。
非阻塞,但高优先级的脚本
大多数时候,这种行为都很好。但有时,你可能希望脚本既以“高”优先级加载,又异步加载。
一个可能的场景是在落地页的英雄部分安装一个小的 SPA。为了保留页面的核心网络指标,特别是LCP和FID(首次输入延迟,很快将被下一个绘制的交互所取代),你需要高度优先这个脚本(毕竟,它负责构建和供电你的应用)。但同时,你不希望它阻止页面的其余部分进行解析。
所以,我们给它一个fetchpriority:
<script src="/script-async.js" async notallow="console.log('async')" fetchpriority="high"></script>
<script src="/script-sync.js" notallow="console.log('sync')"></script>
<script>console.log("inline");</script>
现在,它以提高的优先级下载,同时仍然不阻止页面的其他部分:
控制台验证了这一点。有了更高的优先级,异步脚本加载得更快。在这种情况下,甚至比同步和内联的还要快。
虽然我这里没有特意玩它,但是,是的,fetchpriority 也适用于延迟的脚本。
何时使用
当你提前知道脚本的优先级,并且怀疑浏览器可能没有足够的信息来自行决定时,将 fetchpriority 放在你的脚本上。正如我所提到的,对于你希望以非阻塞、异步的方式加载的脚本,优先化它们特别有帮助。
有意使用
很容易对这样的工具过于热衷,导致过度使用。所以,要小心 - 这样做可能会付出代价。正如俗话所说:“强调一切=强调无。”事实上,过度使用可能实际上使得浏览器更难管理网络争用,损害页面的性能。
MDN 甚至特意在他们的优先级提示文档中指出:
仅在浏览器可能无法自动推断加载资源的最佳方式的特殊情况下使用它。过度使用可能会导致性能下降。
所以,不要因为这些工具存在就觉得有义务使用它们。小心使用。
回顾:何时提示
这里有很多内容,所以让我们快速回顾一下你可能选择使用优先级提示的时机。这些都不是详尽无遗的。只是一些好的开始。
- 当你希望浏览器知道多个晚些时候发现的资源,其中一些比其他资源更对页面至关重要时,提示预加载的资源。
- 提示你知道是用户体验的关键部分的 fetch() 请求,或者可以安全地被降级以为更重要的请求让路。
- 提示你希望尽快加载和显示的首屏图像。
- 提示对页面功能至关重要的脚本,但你不希望阻止页面的其他部分(包括其他资源)被解析和下载。
让浏览器猜得少些
浏览器非常擅长弄清楚如何以及何时下载使我们的页面运行的东西。但它并不总是那么好。它不知道一个页面存在的原因,也不知道它的各个部分背后的意图。所以偶尔,它可以使用一些额外的帮助。
这就是为什么这些优先级提示存在的原因:为了使指令清晰,并且让浏览器很少有机会做出错误的决策。下次当你研究自己应用程序的网络活动时,记住它们,当有意义时,使用它们来帮助使你的页面性能更加智能。