做前端性能優化時,緩存是繞不開的話題。第一次優化電商項目時,發現用户每次打開頁面都要重新加載大量圖片和 JS 文件,加載時間超過 5 秒。後來用了緩存策略,把重複請求的資源緩存起來,首屏加載直接降到 2 秒內,用户體驗提升明顯。
前端緩存主要分兩類:HTTP 緩存(針對網絡請求)和本地存儲(針對前端數據)。前者讓瀏覽器少發請求,後者讓前端少處理數據,兩者配合能大幅提升性能。
一、HTTP 緩存:減少重複請求
HTTP 緩存是瀏覽器和服務器配合的結果,核心是通過響應頭告訴瀏覽器:哪些資源可以緩存、緩存多久、什麼時候需要重新驗證。
1. 強緩存:不發請求直接用緩存
強緩存生效時,瀏覽器直接從本地緩存讀取資源,不會向服務器發請求。關鍵響應頭是 Cache-Control 和 Expires。
- 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-Since 或 ETag/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)建議存在 sessionStorage 或 HttpOnly 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 更安全,但需後端配合。
四、避坑指南
- 緩存更新問題:本地存儲的數據要設過期時間,避免數據陳舊。可在版本號變化時主動清除舊緩存:
// 檢查版本更新,清除舊緩存
const currentVersion = 'v2';
if (localStorage.getItem('appVersion') !== currentVersion) {
localStorage.clear(); // 清除所有舊緩存
localStorage.setItem('appVersion', currentVersion);
}
- 容量限制:
localStorage存太多數據會報錯,可監聽錯誤並降級:
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
} catch (e) {
console.error('存儲失敗,可能容量已滿', e);
// 降級處理:清除部分舊數據再試
}
}
- HTTP 緩存調試:Chrome 開發者工具的 Network 面板,
Size列顯示from memory cache或from disk cache表示強緩存生效;304 Not Modified表示協商緩存生效。 - 避免濫用緩存:實時性要求高的數據(如股票價格)不宜緩存;用户個人數據需注意隱私安全。
總結
前端緩存的核心是“減少重複工作”:HTTP 緩存讓瀏覽器少下資源,本地存儲讓前端少算數據。實際開發中,要根據資源類型和數據特性選擇策略:
- 靜態資源(JS、CSS、圖片):用強緩存 + 哈希文件名,長期緩存。
- 動態接口:用協商緩存,確保數據新鮮。
- 前端數據:臨時數據用
sessionStorage,持久數據用localStorage,大量數據用 IndexedDB。
合理的緩存策略能讓頁面加載速度提升數倍,但也要注意緩存更新和安全問題。緩存不是銀彈,需要結合業務場景靈活調整,才能既保證性能又不犧牲用户體驗。