浏览器在自动设置资源请求优先级方面表现非常出色,但并不总是完美的。优先提示可方便地提供明确的指示,指导网络活动如何进行和在什么情况下发生。
打开浏览器的网络标签,您会看到大量的活动。资源正在被下载,信息正在被提交,事件正在被记录,等等。
在如此多的活动中,有效管理流量的优先级非常关键。带宽争用是真实存在的,当所有请求同时发出时,某些HTTP请求的优先级可能不如其他请求高。例如,如果必须选择,您可能更愿意让某人的付款请求成功完成,而不是只是指示他们尝试的分析请求。而且,您的主要图片尽快出现可能比页脚中的标志图标的呈现更为重要。
幸运的是,浏览器拥有越来越多的工具来帮助优化所有这些网络活动。这些"优先提示"有助于浏览器在资源有限时减少假设,更清晰地决定哪些请求应优先考虑。
这是一套有用的工具,当充分利用时,它们可以对页面性能产生明显的影响,包括那些日益重要的核心 Web 基本指标。让我们探讨其中一些工具,以及它们在哪些场景中最有帮助。
对预加载资产进行优先级排序
现代浏览器具备一种广泛支持的方法,用于通知它们将来会在当前页面上需要的资源:<link rel="preload" ... />。当将其放置在文档的部分时,浏览器被指示尽早以“高”优先级开始下载它。
公平地说,浏览器中的预加载扫描器已经非常擅长这种操作。因此,预加载通常最适用于晚期发现的资源,比如通过内联样式属性加载的背景图像等,这些资源不是直接由您的HTML加载的。但它还对任何其他可能不像您期望的那样被浏览器优先处理的资源非常有用。
例如,Chrome默认以非常高的优先级加载字体,但如果用户处于慢速网络连接上,它会使用备用字体并降低该字体的优先级(顺便说一下,一开始我误以为字体总是具有“最低”优先级,直到与Robin Marx的讨论)。
考虑一个仅通过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>
现在它受到了更多的偏爱:
此外,您可以直接在 link 标记上使用 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是现代Web中最人性化的工具之一。它具有一些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"
});
这次,优先级不同了:
重要的补充:keepalive: true
这里可能存在一个担忧,即“低”优先级的请求可能会丧失,如果用户太快地导航离开页面,这些请求可能会被取消。这是一个合理的问题。根据一些因素,无论是关闭标签还是转到下一页,都可能导致一个重要但相对低优先级的请求被中止。
幸运的是,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。主要图片是一个很好的开始点,它甚至可以对页面的核心 Web 基本指标产生影响,特别是LCP(最大内容绘制)。
对 <script /> 标签进行优先排序
页面上带有src 属性的任何纯粹的 <script /> 标签都将在获取时具有高优先级,但存在一个权衡:它会阻止解析页面的其余部分,直到它被加载和执行。因此,async 属性非常有帮助。它会以低优先级在后台请求脚本,并在准备就绪后立即执行。了解这一点,下面的设置表现出可预测的行为:
<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>
异步脚本的优先级被降低:
控制台确认,随后的脚本在异步脚本加载时被允许解析和执行。
非阻塞但高优先级的脚本
大多数情况下,这种行为都是可以接受的。但有时,您可能希望脚本既以异步方式加载,又以"高"优先级加载。
一个可能的场景是在首页的主要区域中加载一个小型SPA。为了保护页面的核心Web基本指标,特别是LCP和FID(首次输入延迟,即将被互动至下一次绘制所替代),您需要将该脚本设为高优先级(毕竟,它负责构建和驱动您的应用程序)。但与此同时,您不希望它阻止页面的其余部分进行解析。
因此,让我们为它设置一个fetchpriority:
+ <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的一些优先提示文档中也特别强调了这一点:
请谨慎使用,仅在浏览器可能无法自动推断最佳加载方式的特殊情况下使用。过度使用可能导致性能下降。
因此,不要因为这些工具存在而感到必须使用它们。请谨慎使用它们。
回顾:何时使用提示
有很多内容,让我们快速回顾一些您可能选择使用优先提示的情况。这并不是详尽无遗的,只是一些很好的开始点。
-
当您希望浏览器了解多个晚期发现的资源,其中某些资源对页面更为重要时,可以使用预加载提示。
-
在您知道某些请求对用户体验非常重要,或者可以安全地降低优先级以为更重要的请求让路时,可以使用fetch()请求的优先提示。
-
对于您希望尽快加载并可见的屏幕上方图像,可以使用优先提示。
-
对于对页面功能至关重要的脚本,但您不希望阻止页面的其余部分(包括其他资源)进行解析和下载,可以使用脚本的优先提示。
帮助浏览器减少猜测
浏览器在确定如何下载页面所需的内容以及何时下载方面非常智能。但它并不总是完美的。它不知道页面存在的目的或各个部分的意图。因此,有时它可能需要一些额外的帮助。
这就是为什么存在这些优先提示的原因:为了使指示明确,减少浏览器做出错误决策的机会。下次您查看自己应用程序的网络活动时,请记住它们,当有必要时,可以使用它们来提高页面性能的智能程度。