【前端性能優(yōu)化指南】1 - 利用緩存減少遠程請求

更多前端性能優(yōu)化系列請點擊這里 >>

image.png

歡迎來到「前端性能優(yōu)化之旅」的第一站 —— 緩存。

當瀏覽器想要獲取遠程的數據時,我們的性能之旅就開始了。然而,我們并不會立即動身(發(fā)送請求)。在計算機領域,很多性能問題都會通過增加緩存來解決,前端也不例外。和許多后端服務一樣,前端緩存也是多級的。下面讓我們一起來具體看一看。

1. 本地數據存儲

通過結合本地存儲,可以在業(yè)務代碼側實現緩存。

對于一些請求,我們可以直接在業(yè)務代碼側進行緩存處理。緩存方式包括 localStorage、sessionStorage、indexedDB。把這塊加入緩存的討論也許會有爭議,但利用好它確實能在程序側達到一些類似緩存的能力。

例如,我們的頁面上有一個日更新的榜單,我們可以做一個當日緩存:

// 當用戶加載站點中的榜單組件時,可以通過該方法獲取榜單數據
async function readListData() {
    const info = JSON.parse(localStorage.getItem('listInfo'));
    if (isExpired(info.time, +(new Date))) {
        const list = await fetchList();
        localStorage.setItem('listInfo', JSON.stringify({
            time: +(new Date),
            list: list
        }));
        return list;
    }
    return info.list;
}

localStorage 大家都比較了解了,indexedDB 可能會了解的更少一些。想快速了解 indexedDB 使用方式可以看這篇文章[1]

從前端視角看,這是一種本地存儲;但如果從整個系統的維度來看,很多時候其實也是緩存鏈條中的一環(huán)。對于一些特殊的、輕量級的業(yè)務數據,可以考慮使用本地存儲作為緩存。

2. 內存緩存(Memory)

當你訪問一個頁面及其子資源時,有時候會出現一個資源被使用多次,例如圖標。由于該資源已經存儲在內存中,再去請求反而多此一舉,瀏覽器內存則是最近、最快的響應場所。

image.png

內存緩存并無明確的標準規(guī)定,它與 HTTP 語義下的緩存關聯性不大,算是瀏覽器幫我們實現的優(yōu)化,很多時候其實我們意識不到。

對內存緩存感興趣,可以在這篇文章[2]的 Memory Cache 部分進一步了解。

3. Cache API

當我們沒有命中內存緩存時,是否就開始發(fā)送請求了呢?其實不一定。

在這時我們還可能會碰到 Cache API 里的緩存,提到它就不得不提一下 Service Worker 了。它們通常都是配合使用的。

首先明確一下,這層的緩存沒有規(guī)定說該緩存什么、什么情況下需要緩存,它只是提供給了客戶端構建請求緩存機制的能力。如果你對 PWA 或者 Service Worker 很了解,應該非常清楚是怎么一回事。如果不了解也沒有關系,我們可以簡單看一下:

首先,Service Worker 是一個后臺運行的獨立線程,可以在代碼中啟用

// index.js
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw.js').then(function () {
        // 注冊成功
    });
}

之后需要處理一些 Service Worker 的生命周期事件,而其中與這里提到的緩存功能直接相關的則是請求攔截:

// sw.js
self.addEventListener('fetch', function (e) {
    // 如果有cache則直接返回,否則通過fetch請求
    e.respondWith(
        caches.match(e.request).then(function (cache) {
            return cache || fetch(e.request);
        }).catch(function (err) {
            console.log(err);
            return fetch(e.request);
        })
    );
});

以上代碼會攔截所有的網絡請求,查看是否有緩存的請求內容,如果有則返回緩存,否則會繼續(xù)發(fā)送請求。與內存緩存不同,Cache API 提供的緩存可以認為是“永久性”的,關閉瀏覽器或離開頁面之后,下次再訪問仍然可以使用。

Service Worker 與 Cache API 其實是一個功能非常強大的組合,能夠實現堆業(yè)務的透明,在兼容性上也可以做成漸進支持。還是非常推薦在業(yè)務中嘗試的。當然上面代碼簡略了很多,想要進一步了解 Service Worker 和 Cache API 的使用可以看這篇文章[3]。同時推薦使用 Google 的 Workbox。

4. HTTP 緩存

如果 Service Worker 中也沒有緩存的請求信息,那么就會真正到 HTTP request 的階段了。這個時候出現的就是我們所熟知的 HTTP 緩存規(guī)范。

HTTP 有一系列的規(guī)范來規(guī)定哪些情況下需要緩存請求信息、緩存多久,而哪些情況下不能進行信息的緩存。我們可以通過相關的 HTTP 請求頭來實現緩存。

HTTP 緩存大致可以分為強緩存與協商緩存。

4.1. 強緩存

在強緩存的情況下,瀏覽器不會向服務器發(fā)送請求,而是直接從本地緩存中讀取內容,這個“本地”一般就是來源于硬盤。這也就是我們在 Chrome DevTools 上經??吹降摹竏isk cache」。

image.png

與其相關的響應頭則是 ExpiresCache-Control。在 Expires 上可以設置一個過期時間,瀏覽器通過將其與當前本地時間對比,判斷資源是否過期,未過期則直接從本地取即可。而 Cache-Control 則可以通過給它設置一個 max-age,來控制過期時間。例如,max-age=300 就是表示在響應成功后 300 秒內,資源請求會走強緩存。

4.2. 協商緩存

你可能也感覺到了,強緩存不是那么靈活。如果我在 300 秒內更新了資源,需要怎么通知客戶端呢?常用的方式就是通過協商緩存。

我們知道,遠程請求慢的一大原因就是報文體積較大。協商緩存就是希望能通過先“問一問”服務器資源到底有沒有過期,來避免無謂的資源下載。這伴隨的往往會是 HTTP 請求中的 304 響應碼。下面簡單介紹一下實現協商緩存的兩種方式:

一種協防緩存的方式是:服務器第一次響應時返回 Last-Modified,而瀏覽器在后續(xù)請求時帶上其值作為 If-Modified-Since,相當于問服務端:XX 時間點之后,這個資源更新了么?服務器根據實際情況回答即可:更新了(狀態(tài)碼 200)或沒更新(狀態(tài)碼 304)。

上面是通過時間來判斷是否更新,如果更新時間間隔過短,例如 1s 一下,那么使用更新時間的方式精度就不夠了。所以還有一種是通過標識 —— ETag。服務器第一次響應時返回 ETag,而瀏覽器在后續(xù)請求時帶上其值作為 If-None-Match。一般會用文件的 MD5 作為 ETag

作為前端工程師,一定要善于應用 HTTP 緩存。如果想要了解更多關于 HTTP 緩存的內容,可以閱讀這篇文章[4]

上面這些的各級緩存的匹配機制里,都是包含資源的 uri 的匹配,即 uri 更改后不會命中緩存。也正是如此,我們目前在前端實踐中都會把文件 HASH 加入到文件名中,避免同名文件命中緩存的舊資源。

5. Push Cache

假如很不幸,以上這些緩存你都沒有命中,那么你將會碰到最后一個緩存檢查 —— Push Cache。

Push Cache 其實是 HTTP/2 的 Push 功能所帶來的。簡言之,過去一個 HTTP 的請求連接只能傳輸一個資源,而現在你在請求一個資源的同時,服務端可以為你“推送”一些其他資源 —— 你可能在在不久的將來就會用到一些資源。例如,你在請求 www.sample.com 時,服務端不僅發(fā)送了頁面文檔,還一起推送了 關鍵 CSS 樣式表。這也就避免了瀏覽器收到響應、解析到相應位置時才會請求所帶來的延后。

不過 HTTP/2 Push Cache 是一個比較底層的網絡特性,與其他的緩存有很多不同,例如:

  • 當匹配上時,并不會在額外檢查資源是否過期;
  • 存活時間很短,甚至短過內存緩存(例如有文章提到,Chrome 中為 5min 左右);
  • 只會被使用一次;
  • HTTP/2 連接斷開將導致緩存直接失效;
  • ……

如果對 HTTP/2 Push 感興趣,可以看看這篇文章[5]。


好了,到目前為止,我們可能還沒有發(fā)出一個真正的請求。這也意味著,在緩存檢查階段我們就會有很多機會將后續(xù)的性能問題扼殺在搖籃之中 —— 如果遠程請求都不必發(fā)出,又何須優(yōu)化加載性能呢?

所以,審視一下我們的應用、業(yè)務,看看哪些性能問題是可以在源頭上解決的。

不過很多時候,能通過緩存解決的問題只有一部分。所以下面我們會繼續(xù)這趟旅行,目前我們已經有了一個好的開始,不是么?


目前內容已全部更新至 ? fe-performance-journey ? 倉庫中,陸續(xù)會將內容同步到掘金上。如果希望盡快閱讀相關內容,可以直接去該倉庫中瀏覽文章。

喜歡的朋友可以 star 一下,后續(xù)也會繼續(xù)更新更多性能優(yōu)化相關的內容。


參考資料

  1. A quick but complete guide to IndexedDB and storing data in browsers
  2. A Tale of Four Caches
  3. PWA學習與實踐:讓你的WebApp離線可用
  4. 瀏覽器緩存機制:強緩存、協商緩存
  5. HTTP/2 push is tougher than I thought
  6. Caching best practices & max-age gotchas
  7. The Offline Cookbook (Service Worker)
  8. HTTP/2 ORG
  9. Web Caching Explained by Buying Milk at the Supermarket
  10. 深入理解瀏覽器的緩存機制
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 關于 性能優(yōu)化 是個大的面,這篇文章主要涉及到 前端 的幾個點,如 前端性能優(yōu)化 的流程、常見技術手段、工具等。 ...
    子木_lsy閱讀 6,680評論 7 186
  • 原文: https://zhuanlan.zhihu.com/p/44789005 前端緩存/后端緩存 我們先進入...
    謝大見閱讀 1,272評論 0 0
  • 一、前言 緩存可以說是性能優(yōu)化中簡單高效的一種優(yōu)化方式了。一個優(yōu)秀的緩存策略可以縮短網頁請求資源的距離,減少延遲,...
    浪里行舟閱讀 208,987評論 46 521
  • 文.孫亮 我從你的世界路過 一本書 一雙眼 一生的精神交錯 像春雨在靈魂深處滑落 似荷花盡展著你獨有的婀娜 秋葉 ...
    朦朧詩人孫亮閱讀 278評論 2 3
  • 山翠如流,云白飛瀑。 歸來逢迎風雨猝。 攜得黃花一束芳,伴我遙遙前程路。 彎過田園,曲下巷戶。 閑看兒童逸傘步。 ...
    云中仙紫閱讀 242評論 0 2

友情鏈接更多精彩內容