注:本文为译文,原为Get All That Network Activity Under Control with Priority Hints,发布于2023年9月19日。 个别段落翻译不通顺的地方有删减,但是不影响表达整体含义,感兴趣的读者可以阅读英文原文。
浏览器非常擅长对资源请求进行优先级排序。但并不总是做的很好。优先级提示方便地为网络请求提供明确的指示。 打开浏览器的网络标签,你会看到很多活动。资源被下载,信息被提交,事件被记录,等等。有这么多事情要做,有效地管理流量的优先级是非常关键的。 带宽争用是真实存在的,而且当所有 HTTP 请求同时发出时,一些 HTTP 请求的优先级将不如其他请求高。例如,如果你必须做出选择,你可能更希望某人的付款请求成功完成,而不是仅仅表明他们已经尝试过的分析性请求(译者注:例如埋点、记录日志
)。让你的英雄形象图片尽可能快地显示出来,可以说比页面页脚的 logo 渲染更重要。
幸运的是,浏览器拥有越来越多的工具来帮助处理所有这些网络活动的优先级。这些"优先级提示"有助于浏览器在资源有限的情况下做出更少的假设和更清晰的决定,确定哪些请求优先于其他请求。
这是一套有用的工具,如果利用得当,它们可以对页面性能产生切实的影响,包括那些日益重要的核心 Web 关键词。让我们来探索其中的一些,以及它们在哪些场景中最有帮助。
预加载资源的优先级排序
现代浏览器都具有一种受到广泛支持的、可以告知当前页面最终需要的资源的方式: < link rel = " preload".../>
。当被放置在文档的 < head >标签 中时,浏览器会被指示以"高"优先级尽快开始下载。
公平地说,浏览器中的预加载扫描器已经非常擅长这类事情(我之前已经更深入地讨论过这个问题)。出于这个原因,预加载最好用于后期发现的资产------任何不是直接由 HTML 加载的东西。想一想: 一个字体文件加载在一些 CSS,或背景图像加载通过内联样式属性。
考虑仅通过 CSS@font-face 规则加载的字体
css
@font-face {
font-family: "Inter Variable";
src: url("./font.woff2") format("woff2");
}
在加载时,该字体的下载优先级最低,尽管它对页面的视觉体验非常重要:
现在,让我们预载资源:
html
<head>
<!-- Other stuff... -->
<link rel="preload" href="/font.woff2" as="font">
</head>
这样它的优先级会提前:
可以直接在 link 标签上使用fetchpriority
来指示更加明确的信息。当同时预加载多个资源时,它会发出相对优先的信号。
下面是一个人为设计的场景,希望预加载两种字体,其中一种字体加载:
html
<link rel="preload" href="./font-1.woff2" as="font" fetchpriority="low" />
<link rel="preload" href="./font-2.woff2" as="font" fetchpriority="high" />
效果如下:
适用场景
一般来说,当资源不是直接由 HTML 加载时,可以使用预加载,但这对页面的体验至关重要在预加载同一类型的多个资源时使用fetchpriority属性,就可以清楚地了解哪个资源是最重要的。
fetch请求的优先级排序
在我看来,FetchAPI 是现代web中最符合人体工程学的产品之一。它还有一些 XMLHttpRequest 所部具备的优秀特性,比如在传出请求时发出优先级信号的能力。
前面已经提到过一个最重要的用例: 分析性请求(译者注:例如埋点、记录日志
)。当带宽很小且有多个请求时,浏览器将自行决定优先级。但是作为工程师,我们应该知道,你的分析性请求应该让位于对页面中更为关键的其他请求。现代的fetch()技术使这一点变得简单。
下面是一个简单的设置,两个请求(实际上)同时排队:
javascript
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
});
默认情况下,浏览器会自动将它们视为"高"优先级:
现在,我们将显式地告诉浏览器每个请求的优先级:
javascript
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
+ priority: "high"
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
+ priority: "low"
});
这样一来,优先级将会有所不同:
一个用用的配置: keepalive: true
可能存在的一个问题是,"低"优先级请求可能会被取消,如果用户过早地从页面导航离开。但这是一个合法的问题。在一些情况下,例如关闭选项卡或移动到下一页可能导致一个重要的,但相对低优先级的请求被中止。
好在fetch ()接受 keepalive 选项。当设置为 true 时,即使在页面终止时,浏览器也会完成该请求。
适用场景
当多个请求正在并发执行时,可以指示fetch()的优先级,开发者应该很清楚哪个请求是最重要的。
请求优先级排序
我们不做任何特别的事情,浏览器将尽最大努力确定页面上最重要的图像。为了说明这一点,我加载了下面的图像,彼此之间间隔很好的距离,所以只有一个是会被明显看到的(译者注:位于页面顶部,位置显眼
)。
html
<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 时,事情变得更可预测了:
html
<img src="./cat-1.jpeg" fetchpriority="high" />
cat-1.jpeg一开始就以最高优先级加载。浏览器在确定资源关键度方面很智能,但是它受益于清晰的指令。如果你知道一张图片很重要,那么就要明确表达出来。
这也可以和懒加载很好的结合在一起使用:
html
<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" />
这样要来,浏览器就可以准确地知道如何(以及是否)加载图像。在如上例子中,它甚至不会在初始加载时开始请求屏幕视口外的图像。相反,它将等待,直到他们更接近观察窗口。
适用场景
当您知道图像对页面体验非常重要时,可以在图像上使用显式的 fetch 优先级。
页面上具有 src 属性的普通 < script/> 在获取时都会获得高优先级。但注意一点: 它阻止对页面的其余部分进行解析,直到它被加载并执行。介于此,异步属性很有帮助,它将在后台以低优先级请求脚本,并在准备好后立即执行。了解了这一点,以下设置的行为是可预测的:
html
<script src="/script-async.js" async onload="console.log('async')"></script>
<script src="/script-sync.js" onload="console.log('sync')"></script>
<script>console.log("inline");</script>
异步脚本的优先级被降低:
可以通过控制台确认在加载异步脚本时可以解析和执行后续脚本:
非阻塞但是高优先级的script标签:
某些情况下,可能希望脚本同时异步加载并具有"高"优先级,所以可以为其赋予较高的优先级:
html
<script src="/script-async.js" async onload="console.log('async')" fetchpriority="high"></script>
<script src="/script-sync.js" onload="console.log('sync')"></script>
<script>console.log("inline");</script>
现在其加载优先级提高了,同时仍然没有阻塞页面的其余部分:
控制台验证了这一点,有了这种更高的优先级,异步脚本的加载速度就会更快,甚至在同步和内联之前:
适用场景
当我们事先知道了脚本的优先级,并且不确定浏览器本身有没有足够的信息来确定优先级时,可以在脚本上放置 fetchpriority 。它对于以非阻塞、异步方式加载的脚本的优先级排序特别有帮助。
请谨慎使用
对于这样的工具,人们很容易变得有点过于热心,从而导致过度使用。所以,要小心地使用,因为这样做可能会付出代价。事实上,过度使用实际上可能会使浏览器更难以管理网络争用,从而损害页面的性能。
MDN 甚至在他们的一些优先级提示文档中指出了这一点:
谨慎使用它,可以在浏览器可能无法推断出以最佳的方式自动加载资源的特殊情况下使用,但是过度使用会导致性能下降。
因此,不要因为这些工具的存在而感到有义务去使用它们,要小心使用它们。
温习:何时使用提示
内容有点多,所以让我们快速回顾一下您可能选择利用优先级提示的一些时候。我们不详细说了,就说一下重要的地方:
(1)当您希望浏览器知道多个延迟发现的资源中,一些资源对页面的影响比其他资源更重要时,可以提示预加载的资源。
(2)fetch()请求提示要么是用户体验的重要组成部分,要么可以安全地放弃一些请求的优先级,为更重要的请求让路。
(3)提示位置显眼的图片可以被尽快加载和尽快被看见。
(4)提示影响页面中关键功能脚本被尽快加载,但是您不希望阻止解析和下载页面的其余部分(包括其他资源)
尽可能少的让浏览器去猜测
浏览器非常的聪明,知道如何以及何时下载那些让我们的页面运转的东西。但并不总是表现的很好。它不知道页面为什么存在,也不知道其各个部分背后的意图。所以某些情况下它会需要一些额外的帮助。
这就是优先级提示存在的原因: 使指令更清晰,并使浏览器很少有机会做出错误的决定。下次在处理自己的应用程序的网络活动时,请记住要让优先级提示发挥价值,使用它们来帮助提高页面的性能。