做前端性能優化時,緩存是繞不開的話題。第一次優化電商項目時,發現用户每次打開頁面都要重新加載大量圖片和 JS 文件,加載時間超過 5 秒。後來用了緩存策略,把重複請求的資源緩存起來,首屏加載直接降到 2 秒內,用户體驗提升明顯。

前端緩存主要分兩類:HTTP 緩存(針對網絡請求)和本地存儲(針對前端數據)。前者讓瀏覽器少發請求,後者讓前端少處理數據,兩者配合能大幅提升性能。

一、HTTP 緩存:減少重複請求

HTTP 緩存是瀏覽器和服務器配合的結果,核心是通過響應頭告訴瀏覽器:哪些資源可以緩存、緩存多久、什麼時候需要重新驗證。

1. 強緩存:不發請求直接用緩存

強緩存生效時,瀏覽器直接從本地緩存讀取資源,不會向服務器發請求。關鍵響應頭是 Cache-ControlExpires

  • Cache-Control(推薦):HTTP/1.1 新增,優先級高於 Expires
  • max-age=3600:資源緩存 3600 秒(1 小時)
  • public:客户端和代理服務器都可緩存
  • private:僅客户端可緩存(默認)
  • no-cache:不使用強緩存,需協商緩存驗證
  • no-store:完全不緩存
  • Expires:HTTP/1.0 產物,值是絕對時間(如 Expires: Wed, 21 Oct 2025 07:28:00 GMT),缺點是依賴客户端時間,可能不準。

實戰配置:對靜態資源(圖片、JS、CSS)設置較長的 max-age

# Nginx 配置示例
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
  expires 7d; # 等價於 Cache-Control: max-age=604800(7*24*3600)
  add_header Cache-Control "public";
}

這樣瀏覽器會緩存這些資源 7 天,7 天內重複訪問不會發請求。

2. 協商緩存:問服務器是否能用緩存

強緩存過期後,瀏覽器會發請求到服務器,通過 Last-Modified/If-Modified-SinceETag/If-None-Match 驗證資源是否更新:

  • Last-Modified 方案
  • 服務器返回 Last-Modified: Wed, 15 Nov 2023 12:00:00 GMT(資源最後修改時間)
  • 瀏覽器下次請求時帶 If-Modified-Since: 相同時間
  • 服務器對比:未修改返回 304(用緩存),已修改返回 200(新資源)
  • ETag 方案
  • 服務器返回 ETag: "abc123"(資源唯一標識,如哈希值)
  • 瀏覽器下次請求帶 If-None-Match: "abc123"
  • 服務器對比:標識相同返回 304,不同返回 200

實戰建議:動態內容(如 API 接口)用協商緩存,避免強緩存導致數據不更新:

# API 接口配置協商緩存
location /api/ {
  add_header Cache-Control "no-cache"; # 禁用強緩存
  # 由後端程序設置 Last-Modified 或 ETag
}

3. 緩存失效策略:解決更新問題

強緩存最大的問題是:資源更新了,但瀏覽器還在用舊緩存。解決方案是給資源加版本號或哈希值:

<!-- 不好:修改後瀏覽器可能還用舊緩存 -->
<script src="app.js"></script>

<!-- 好:文件名帶哈希,內容變則哈希變,緩存自動失效 -->
<script src="app.8f3d7.js"></script>

Webpack、Vite 等構建工具可自動生成帶哈希的文件名,完美解決緩存失效問題。

二、本地存儲:前端數據緩存

HTTP 緩存針對網絡請求,而本地存儲用於緩存前端生成的數據(如用户信息、列表數據),減少重複計算或請求。

1. localStorage:持久化存儲

  • 特點:永久存儲(除非手動清除),容量約 5-10MB,字符串鍵值對。
  • 適用場景:用户偏好設置、不常變的靜態數據。
// 存儲數據
localStorage.setItem('theme', 'dark');
localStorage.setItem('userInfo', JSON.stringify({ id: 1, name: '張三' }));

// 讀取數據
const theme = localStorage.getItem('theme');
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');

// 刪除數據
localStorage.removeItem('theme');
// 清空所有
localStorage.clear();

注意localStorage 是同步操作,大量數據會阻塞主線程;且數據永久存儲,敏感信息別存這裏。

2. sessionStorage:會話級存儲

  • 特點:僅在當前會話(標籤頁)有效,關閉標籤頁後清除,容量約 5MB。
  • 適用場景:臨時表單數據、頁面間臨時傳遞數據。
// 存儲臨時表單數據
sessionStorage.setItem('formDraft', JSON.stringify({ username: '張三', age: 20 }));

// 跨頁面讀取(同會話內)
// 在另一個頁面中
const draft = JSON.parse(sessionStorage.getItem('formDraft') || '{}');

3. sessionStorage:會話級存儲

  • 特點:僅在當前會話(標籤頁)有效,關閉標籤頁後清除,容量約 5MB。
  • 適用場景:臨時表單數據、頁面間臨時傳遞數據。
// 存儲臨時表單數據
sessionStorage.setItem('formDraft', JSON.stringify({ username: '張三', age: 20 }));

// 跨頁面讀取(同會話內)
// 在另一個頁面中
const draft = JSON.parse(sessionStorage.getItem('formDraft') || '{}');

4. IndexedDB:大型結構化數據

  • 特點:異步操作,容量大(一般無上限),支持複雜查詢,類似前端數據庫。
  • 適用場景:離線應用、大量數據緩存(如離線地圖、歷史記錄)。
// 簡單示例:打開數據庫並存儲數據
const request = window.indexedDB.open('myDB', 1);

// 數據庫版本更新時創建表
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // 創建存儲對象(表)
  if (!db.objectStoreNames.contains('products')) {
    db.createObjectStore('products', { keyPath: 'id' });
  }
};

request.onsuccess = (event) => {
  const db = event.target.result;
  // 開啓事務
  const transaction = db.transaction(['products'], 'readwrite');
  const store = transaction.objectStore('products');
  
  // 存儲數據
  store.add({ id: 1, name: '手機', price: 3999 });
  
  // 讀取數據
  store.get(1).onsuccess = (e) => {
    console.log('查詢結果', e.target.result);
  };
};

IndexedDB API 較複雜,實際開發可使用 localForage 等庫簡化操作。

三、實戰場景:組合使用策略

1. 列表數據緩存

用户頻繁訪問的列表(如商品列表),可結合 HTTP 緩存和本地存儲:

// 封裝帶緩存的請求函數
async function getProductList() {
  // 先查本地緩存
  const cacheKey = 'productList_v1'; // 加版本號方便更新
  const cached = localStorage.getItem(cacheKey);
  
  if (cached) {
    const { data, expireTime } = JSON.parse(cached);
    // 緩存未過期(10分鐘)
    if (Date.now() < expireTime) {
      return data;
    }
  }
  
  // 緩存過期或無緩存,發請求
  const response = await fetch('/api/products');
  const data = await response.json();
  
  // 存入本地緩存,設置10分鐘過期
  localStorage.setItem(cacheKey, JSON.stringify({
    data,
    expireTime: Date.now() + 10 * 60 * 1000
  }));
  
  return data;
}

2. 登錄狀態管理

登錄令牌(Token)建議存在 sessionStorageHttpOnly Cookie 中:

// 登錄成功後存儲 Token
function handleLogin(token) {
  // 會話級存儲,關閉頁面失效,相對安全
  sessionStorage.setItem('token', token);
}

// 請求時攜帶 Token
async function fetchWithToken(url) {
  const token = sessionStorage.getItem('token');
  return fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
}

安全提示:敏感信息別存 localStorage,可能被 XSS 竊取;HttpOnly Cookie 更安全,但需後端配合。

四、避坑指南

  1. 緩存更新問題:本地存儲的數據要設過期時間,避免數據陳舊。可在版本號變化時主動清除舊緩存:
// 檢查版本更新,清除舊緩存
const currentVersion = 'v2';
if (localStorage.getItem('appVersion') !== currentVersion) {
  localStorage.clear(); // 清除所有舊緩存
  localStorage.setItem('appVersion', currentVersion);
}
  1. 容量限制localStorage 存太多數據會報錯,可監聽錯誤並降級:
function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, value);
  } catch (e) {
    console.error('存儲失敗,可能容量已滿', e);
    // 降級處理:清除部分舊數據再試
  }
}
  1. HTTP 緩存調試:Chrome 開發者工具的 Network 面板,Size 列顯示 from memory cachefrom disk cache 表示強緩存生效;304 Not Modified 表示協商緩存生效。
  2. 避免濫用緩存:實時性要求高的數據(如股票價格)不宜緩存;用户個人數據需注意隱私安全。

總結

前端緩存的核心是“減少重複工作”:HTTP 緩存讓瀏覽器少下資源,本地存儲讓前端少算數據。實際開發中,要根據資源類型和數據特性選擇策略:

  • 靜態資源(JS、CSS、圖片):用強緩存 + 哈希文件名,長期緩存。
  • 動態接口:用協商緩存,確保數據新鮮。
  • 前端數據:臨時數據用 sessionStorage,持久數據用 localStorage,大量數據用 IndexedDB。

合理的緩存策略能讓頁面加載速度提升數倍,但也要注意緩存更新和安全問題。緩存不是銀彈,需要結合業務場景靈活調整,才能既保證性能又不犧牲用户體驗。