前言
今天我們來講一講 Web 儲存有幾種機制,並弄清楚什麼是臨時儲存,什麼是持久儲存。
你可能不知道的是:我們平常口中所說的持久儲存 localStorage 很多時候其實是系統級別的“臨時儲存”。
正文
IndexedDB
Indexed DB 的操作是非同步的,不會阻塞主執行緒的執行,可以在 window、web workers、service workers 環境中使用。
IndexedDB 是基於檔案儲存的,API 較為複雜,包含 v1 v2 的差異,建議通過類庫來使用,比如:Dexie.js。
Cache Storage API
Cache Storage API 為快取的 Request/Response 物件提供儲存機制,常在 ServiceWorker 中應用。
非同步,不會阻塞主執行緒的執行,可以在 window、web workers、service workers 環境中使用。
SessionStorage
同步,會阻塞主執行緒的執行。
一般用於儲存臨時性的少量的資料。
SessionStorage 是標籤級別的,跟隨者標籤的生命週期,並且會隨著標籤的銷燬而清空資料。
無法在 web workers、service workers 環境中使用。
它只能儲存字串,大小限制大約為 5MB。
LocalStorage
同步,會阻塞主執行緒的執行。
無法在 web workers、service workers 環境中使用。
它只能儲存字串,大小限制大約為 5MB。
Cookies
Cookies 有它的用途,但不適用於儲存資料。
Cookie 會在每次 HTTP 請求的時候攜帶在請求頭中,大體積的 Cookies 會顯著增加 HTTP 請求的負擔。
Cookies 讀寫是同步的,只能儲存字串,並且無法在 web workers 環境中使用。
File System API
File System API 和 FileWriter API 提供讀取或寫入檔案到沙箱中(Sandboxed file system)。
它是非同步的,不推薦使用,因為 File System API 只能在 Chromium 核心中使用。
File System Access API
File System Access API 設計用於便捷得讀取和編輯本地檔案。
但在讀取或寫入本地檔案的時候,需要獲得使用者授權,並且授權狀態無法持久化記錄。
Application Cache
Application Cache 已被棄用,不建議使用。
建議遷移至 service workers 或 Cache API。
Storage 可以使用多少磁碟空間?
- Chrome 允許使用 80% 的硬碟空間,單一的源(域名)可以使用 60% 的硬碟空間,可以通過 StorageManager API 檢測最大的硬碟空間限額,其他基於 Chromium 核心的瀏覽器有不一樣的限制,可能會允許使用更多的硬碟空間,檢視更多實現 PR #3896
- Internet Explorer 10(IE 10)及以上,最多可以儲存 250MB,並在超過 10MB 的時候會提示使用者
- Firefox 允許使用 50% 的空閒硬碟空間,單個一級域名最多可以使用 2GB 硬碟空間,可以通過 StorageManager API 檢測最大的硬碟空間限額
- Safari 允許使用 1GB,當達到 1GB 的時候會提示使用者(該資料可能不準確,沒有找到 Safari 官方文件)
現代瀏覽器大多數已經不會再提示使用者以授權更多的儲存空間了。
如何檢測儲存空間是否可用?
在大多數瀏覽器中,可以通過 StorageManager API 檢測儲存空間總量與正在使用的量
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
// quota data
{
"quota": 299977904946,
"usage": 27154039,
"usageDetails": {
"caches": 26813093,
"indexedDB": 305864,
"serviceWorkerRegistrations": 35082
}
}
注意:
- 並不是所有瀏覽器都實現了,因此使用之前需要先判斷相容性
- 需要捕獲並處理超過配額限額的錯誤
IndexedDB 超限處理
indexedDB 超限將會執行 onabort 回撥,並丟擲一個 DOMException 錯誤,需要處理它的 QuotaExceededError 異常。
const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
const error = event.target.error; // DOMException
if (error.name == 'QuotaExceededError') {
// Fallback code goes here
}
};
Cache API 超限處理
丟擲一個 Promise Rejection,QuotaExceededError 錯誤物件,需要處理它的 QuotaExceededError 異常。
try {
const cache = await caches.open('my-cache');
await cache.add(new Request('/sample1.jpg'));
} catch (err) {
if (error.name === 'QuotaExceededError') {
// Fallback code goes here
}
}
瀏覽器什麼時候回收儲存空間?
Web Storage 分為兩種儲存模式,分別是:臨時儲存 Best Effort 和持久儲存 Persistent。
預設情況下網站資料(包括 IndexedDB, Cache API, LocalStorage 等)都儲存在臨時儲存 Best Effort 中,會在儲存空間不足的時候被瀏覽器清除掉。
各個瀏覽器回收儲存空間的差異:
- Chrome 當瀏覽器儲存空間不足時,會優先清除最近最少使用的資料,逐個清除,直至不再超限
- IE 10+ 不會自動清除資料,但會阻止站點繼續寫入資料
- Firefox 當磁碟空間充滿時,會優先清除最近最少使用的資料,逐個清除,直至不再超限
- Safari(iOS、iPadOS、MacOS) 會自動清除超過 7 天以上的資料,但不會清除“已新增至主螢幕”的網站和“PWA”網站
申請和檢視持久儲存 Persistent Storage
申請持久儲存 Persistent Storage:
// Request persistent storage for site
if (navigator.storage && navigator.storage.persist) {
const isPersisted = await navigator.storage.persist();
console.log(`Persisted storage granted: ${isPersisted}`);
}
檢視持久儲存 Persistent Storage 授權狀態:
// Check if site's storage has been marked as persistent
if (navigator.storage && navigator.storage.persist) {
const isPersisted = await navigator.storage.persisted();
console.log(`Persisted storage granted: ${isPersisted}`);
}
各個瀏覽器申請持久儲存 Persistent Storage 的差異:
在 Chrome 55 以後,申請持久儲存只需要滿足以下任一條件,即可自動獲得持久儲存許可權,無需使用者確認:
- 該站點已新增書籤, 並且使用者的書籤數小於等於5個
- 站點有很高的"site engagement",通過這個命令可以檢視: chrome://site-engagement/
- 站點已新增到主螢幕
- 站點啟用了push通知功能
- 在 Firefox 中,會提示使用者授權
最後測試並驗證:
- 開啟 https://baidu.com,開啟控制檯輸入
await navigator.storage.persist()
,返回true
- 開啟 https://wy.guahao.com,開啟控制檯輸入
await navigator.storage.persist()
,返回false