如何通過使用優(yōu)先級(jí)提示,來(lái)控制所有網(wǎng)頁(yè)資源加載順序
當(dāng)你打開瀏覽器的網(wǎng)絡(luò)標(biāo)簽時(shí),你會(huì)看到大量的活動(dòng)。資源正在下載,信息正在提交,事件正在記錄,等等。
由于有太多的活動(dòng),有效地管理這些流量的優(yōu)先級(jí)變得至關(guān)重要。帶寬爭(zhēng)用是真實(shí)存在的,當(dāng)所有請(qǐng)求同時(shí)觸發(fā)時(shí),有些HTTP請(qǐng)求的優(yōu)先級(jí)并不像其他請(qǐng)求那樣高。例如,如果你必須選擇,你可能更希望某人的付款請(qǐng)求成功完成,而不是僅僅表示他們嘗試過的分析請(qǐng)求。而讓你的主要圖片盡快顯示無(wú)疑比在頁(yè)面底部渲染你的標(biāo)志更為重要。
幸運(yùn)的是,瀏覽器擁有越來(lái)越多的工具來(lái)幫助優(yōu)先處理所有這些網(wǎng)絡(luò)活動(dòng)。這些“優(yōu)先級(jí)提示”幫助瀏覽器在資源有限時(shí),對(duì)哪些請(qǐng)求應(yīng)該優(yōu)先處理做出更少的假設(shè)和更明確的決策。
這是一套有用的工具,當(dāng)它們得到很好的利用時(shí),它們可以對(duì)頁(yè)面性能產(chǎn)生實(shí)質(zhì)性的影響,包括那些越來(lái)越重要的核心網(wǎng)絡(luò)指標(biāo)。讓我們探索其中的一些,以及它們最有幫助的一些場(chǎng)景。
這是一套有用的工具,當(dāng)它們得到很好的利用時(shí),它們可以對(duì)頁(yè)面性能產(chǎn)生實(shí)質(zhì)性的影響,包括那些越來(lái)越重要的核心網(wǎng)絡(luò)指標(biāo)。讓我們探索其中的一些,以及它們最有幫助的一些場(chǎng)景。
優(yōu)先加載的資源
現(xiàn)代瀏覽器有一個(gè)受到良好支持的方法,可以告訴瀏覽器當(dāng)前頁(yè)面最終需要哪些資源:<link rel="preload" ... />。當(dāng)它放在文檔的<head>中時(shí),瀏覽器會(huì)被指示盡快以“高”優(yōu)先級(jí)下載它。
公平地說,瀏覽器中的預(yù)加載掃描器已經(jīng)非常擅長(zhǎng)這方面的工作。因此,預(yù)加載通常最適用于晚些時(shí)候發(fā)現(xiàn)的資源 - 任何不直接由你的HTML加載的東西,比如通過內(nèi)聯(lián)樣式屬性加載的背景圖像。但它也適用于任何其他可能不像你希望的那樣被瀏覽器優(yōu)先考慮的東西。
例如:默認(rèn)情況下,Chrome 會(huì)以非常高的優(yōu)先級(jí)加載字體,但如果某人的網(wǎng)絡(luò)連接速度很慢,它會(huì)使用備用字體并降低該優(yōu)先級(jí)。
考慮一個(gè)僅通過CSS @font-face規(guī)則加載的字體:
@font-face {
font-family: "Inter Variable";
src: url("./font.woff2") format("woff2");
}
在加載時(shí),由于網(wǎng)絡(luò)連接慢,該字體獲得了最低的下載優(yōu)先級(jí),盡管它對(duì)于頁(yè)面的視覺體驗(yàn)非常重要。
但我們可以通過預(yù)加載該資源來(lái)覆蓋瀏覽器的決定:
<head>
<!-- Other stuff... -->
<link rel="preload" href="/font.woff2" as="font">
</head>
現(xiàn)在它更受歡迎了:
你可以直接在鏈接標(biāo)簽上使用 fetchpriority 來(lái)明確指示相對(duì)優(yōu)先級(jí),這在同時(shí)預(yù)加載多個(gè)資源時(shí)非常有用。
這是一個(gè)假設(shè)的場(chǎng)景,你想預(yù)加載兩種字體,但想讓其中一種優(yōu)先于另一種:
<link rel="preload" href="./font-1.woff2" as="font" fetchpriority="low" />
<link rel="preload" href="./font-2.woff2" as="font" fetchpriority="high" />
網(wǎng)絡(luò)活動(dòng)的結(jié)果會(huì)反映這些指示。
何時(shí)使用
通常,當(dāng)資源不直接由HTML加載,但對(duì)頁(yè)面的體驗(yàn)至關(guān)重要時(shí)(例如字體、CSS背景圖像等),使用預(yù)加載。當(dāng)預(yù)加載多種同類型的資源,且你明確知道哪個(gè)最重要時(shí),加入fetchpriority屬性。
優(yōu)先化 fetch() 請(qǐng)求
我認(rèn)為,F(xiàn)etch API 是現(xiàn)代網(wǎng)絡(luò)的最佳工具之一。與 XMLHttpRequest 相比,它有一些很好的功能,比如在外發(fā)請(qǐng)求時(shí)發(fā)出優(yōu)先信號(hào)的能力。
最容易想到的用例是:分析請(qǐng)求。當(dāng)帶寬有限并且有多個(gè)請(qǐng)求在執(zhí)行時(shí),瀏覽器會(huì)自行決定優(yōu)先級(jí)。但我們作為工程師應(yīng)該知道,通常的分析請(qǐng)求應(yīng)該優(yōu)先于頁(yè)面目的更為關(guān)鍵的其他請(qǐng)求。現(xiàn)代的fetch()使這變得簡(jiǎn)單。
下面是兩個(gè)請(qǐng)求幾乎同時(shí)入隊(duì)的簡(jiǎn)單設(shè)置:
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
});
默認(rèn)情況下,瀏覽器會(huì)自動(dòng)將它們都視為 "高 "優(yōu)先級(jí):
現(xiàn)在,我們要明確地告訴瀏覽器每個(gè)請(qǐng)求的優(yōu)先級(jí):
fetch("http://localhost:8000/pay", {
method: "POST",
body: paymentBody,
+ priority: "high"
});
fetch("http://localhost:8000/log", {
method: "POST",
body: loggingBody,
+ priority: "low"
});
這次,優(yōu)先級(jí)是不同的:
可能的擔(dān)憂是"low"優(yōu)先級(jí)的請(qǐng)求可能會(huì)丟失 - 如果用戶過早離開頁(yè)面,請(qǐng)求可能會(huì)被取消。這是一個(gè)真正的問題。根據(jù)幾個(gè)因素,關(guān)閉標(biāo)簽頁(yè)或轉(zhuǎn)到下一個(gè)頁(yè)面可能導(dǎo)致一個(gè)重要但相對(duì)低優(yōu)先級(jí)的請(qǐng)求被中止。
幸運(yùn)的是,fetch() 還接受一個(gè) keepalive 選項(xiàng)。當(dāng)設(shè)置為true時(shí),即使頁(yè)面終止,瀏覽器也會(huì)完成該請(qǐng)求。
何時(shí)使用
當(dāng)你知道多個(gè)請(qǐng)求正在并發(fā)執(zhí)行,并且你明確知道哪個(gè)最重要(或哪個(gè)可以安全地被降級(jí))時(shí),指示fetch()的優(yōu)先級(jí)。
優(yōu)先化<img />請(qǐng)求
如果我們不做任何特殊處理,瀏覽器會(huì)盡量確定頁(yè)面上最重要的圖像。為了說明這一點(diǎn),我加載了以下圖像,它們之間的距離很大,所以只有一個(gè)會(huì)在"頁(yè)面首部"顯示。
<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" />
瀏覽器發(fā)現(xiàn)了哪個(gè)最重要,但這花了一秒鐘。當(dāng)開始下載時(shí),這三者都是“低”優(yōu)先級(jí)。但很快,頁(yè)面首部的那個(gè)切換到了“高”優(yōu)先級(jí)。
當(dāng)我為第一張圖片添加fetchpriority屬性時(shí),情況變得更加可預(yù)測(cè):
<img src="./cat-1.jpeg" fetchpriority="high" />
此后,cat-1.jpeg 從一開始就以最高的優(yōu)先級(jí)加載。雖然最初令人費(fèi)解,但這是有道理的。瀏覽器非常擅長(zhǎng)確定資源的關(guān)鍵性,但它從明確的指示中受益。如果你知道一張圖片很重要,就明確說明。
順便說一句,這個(gè)特性與本地圖像延遲加載非常搭,這是現(xiàn)在非常受支持的特性。
<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" />
有了這個(gè),瀏覽器就知道如何加載圖像,只在合適的時(shí)候加載。在我的情況下,它甚至不會(huì)開始請(qǐng)求初始加載時(shí)屏幕外的圖像。相反,它會(huì)等到它們更接近視口。
何時(shí)使用
當(dāng)你知道它們對(duì)頁(yè)面體驗(yàn)非常重要時(shí),對(duì)圖像使用明確的fetchpriority。主圖像是一個(gè)很好的開始,它甚至可以影響頁(yè)面的核心網(wǎng)絡(luò)指標(biāo) - 特別是LCP(最大內(nèi)容繪制)。
優(yōu)先化 <script /> 標(biāo)簽
頁(yè)面上帶有src屬性的任何普通<script />在獲取時(shí)都會(huì)得到高優(yōu)先級(jí),但這有一個(gè)權(quán)衡:在它加載并執(zhí)行之前,它會(huì)阻止解析頁(yè)面的其余部分。出于這個(gè)原因,async屬性很有用。它會(huì)以低優(yōu)先級(jí)在后臺(tái)請(qǐng)求腳本,并在準(zhǔn)備好后立即執(zhí)行。知道這一點(diǎn),以下設(shè)置行為是可預(yù)測(cè)的:
<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>
異步腳本在優(yōu)先級(jí)中被降低:
控制臺(tái)確認(rèn),在 async 腳本加載過程中,允許解析和執(zhí)行后續(xù)腳本。
非阻塞,但高優(yōu)先級(jí)的腳本
大多數(shù)時(shí)候,這種行為都很好。但有時(shí),你可能希望腳本既以“高”優(yōu)先級(jí)加載,又異步加載。
一個(gè)可能的場(chǎng)景是在落地頁(yè)的英雄部分安裝一個(gè)小的 SPA。為了保留頁(yè)面的核心網(wǎng)絡(luò)指標(biāo),特別是LCP和FID(首次輸入延遲,很快將被下一個(gè)繪制的交互所取代),你需要高度優(yōu)先這個(gè)腳本(畢竟,它負(fù)責(zé)構(gòu)建和供電你的應(yīng)用)。但同時(shí),你不希望它阻止頁(yè)面的其余部分進(jìn)行解析。
所以,我們給它一個(gè)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>
現(xiàn)在,它以提高的優(yōu)先級(jí)下載,同時(shí)仍然不阻止頁(yè)面的其他部分:
控制臺(tái)驗(yàn)證了這一點(diǎn)。有了更高的優(yōu)先級(jí),異步腳本加載得更快。在這種情況下,甚至比同步和內(nèi)聯(lián)的還要快。
雖然我這里沒有特意玩它,但是,是的,fetchpriority 也適用于延遲的腳本。
何時(shí)使用
當(dāng)你提前知道腳本的優(yōu)先級(jí),并且懷疑瀏覽器可能沒有足夠的信息來(lái)自行決定時(shí),將 fetchpriority 放在你的腳本上。正如我所提到的,對(duì)于你希望以非阻塞、異步的方式加載的腳本,優(yōu)先化它們特別有幫助。
有意使用
很容易對(duì)這樣的工具過于熱衷,導(dǎo)致過度使用。所以,要小心 - 這樣做可能會(huì)付出代價(jià)。正如俗話所說:“強(qiáng)調(diào)一切=強(qiáng)調(diào)無(wú)?!笔聦?shí)上,過度使用可能實(shí)際上使得瀏覽器更難管理網(wǎng)絡(luò)爭(zhēng)用,損害頁(yè)面的性能。
MDN 甚至特意在他們的優(yōu)先級(jí)提示文檔中指出:
僅在瀏覽器可能無(wú)法自動(dòng)推斷加載資源的最佳方式的特殊情況下使用它。過度使用可能會(huì)導(dǎo)致性能下降。
所以,不要因?yàn)檫@些工具存在就覺得有義務(wù)使用它們。小心使用。
回顧:何時(shí)提示
這里有很多內(nèi)容,所以讓我們快速回顧一下你可能選擇使用優(yōu)先級(jí)提示的時(shí)機(jī)。這些都不是詳盡無(wú)遺的。只是一些好的開始。
- 當(dāng)你希望瀏覽器知道多個(gè)晚些時(shí)候發(fā)現(xiàn)的資源,其中一些比其他資源更對(duì)頁(yè)面至關(guān)重要時(shí),提示預(yù)加載的資源。
- 提示你知道是用戶體驗(yàn)的關(guān)鍵部分的 fetch() 請(qǐng)求,或者可以安全地被降級(jí)以為更重要的請(qǐng)求讓路。
- 提示你希望盡快加載和顯示的首屏圖像。
- 提示對(duì)頁(yè)面功能至關(guān)重要的腳本,但你不希望阻止頁(yè)面的其他部分(包括其他資源)被解析和下載。
讓瀏覽器猜得少些
瀏覽器非常擅長(zhǎng)弄清楚如何以及何時(shí)下載使我們的頁(yè)面運(yùn)行的東西。但它并不總是那么好。它不知道一個(gè)頁(yè)面存在的原因,也不知道它的各個(gè)部分背后的意圖。所以偶爾,它可以使用一些額外的幫助。
這就是為什么這些優(yōu)先級(jí)提示存在的原因:為了使指令清晰,并且讓瀏覽器很少有機(jī)會(huì)做出錯(cuò)誤的決策。下次當(dāng)你研究自己應(yīng)用程序的網(wǎng)絡(luò)活動(dòng)時(shí),記住它們,當(dāng)有意義時(shí),使用它們來(lái)幫助使你的頁(yè)面性能更加智能。