前言
目前瀏覽器的儲存機制有很多,如:indexedDB、localStorage、sessionStorage、File System API、applicationCache 等等,那為什麼又制定了一套 Cache API 呢?對比其他儲存機制有什麼優勢?
簡介
Cache API 是一套搭配 PWA serviceworker 賦能的儲存機制,來實現請求資料離線功能。與 applicationCache 相似,提供了力度極細的儲存控制能力,內容完全由指令碼控制。常在 serviceworker 中搭配 Fetch 使用,且同一個 URL 不同 header 可以儲存多個 Response。不提供跨域共享,且與HTTP快取完全隔離。
與其他儲存機制的區別:
- Cache API 是非同步化的儲存方式,serviceworker中必須使用非同步化儲存。
- Cache API 是以 Requst 做為 key,Response 做為 value 進行儲存的,非同步化的 IndexDB 基於結構化克隆儲存, 無法儲存流式資料,轉換成本過高,增大記憶體使用及影響速度。
CacheStorage 方法
CacheStorage 是 Cache 物件儲存的介面,可以通過兩種方式獲取:
ServiceWorkerGlobalScope.caches
Window.caches
複製程式碼
注: 必須在 https 環境下才能使用。
下面都是在 ServiceWorkerGlobalScope 環境下。
open
語法:
// cachs 是 CacheStorage 例項的只讀全域性變數
caches.open(cacheName).then(cache => {
// 處理開啟的 cache 例項相關操作
});
複製程式碼
開啟(如果沒有 cacheName,則建立)為 cacheName 的 Cache 例項。
返回 Promise,resolve 為Cache 例項。
delete
語法:
caches.delete(cacheName).then(boolean => {
// true: cache 發現並已經刪除
});
複製程式碼
查詢匹配 cacheName 的 Cache 物件。找到則刪除 Cache 物件並返回一個 resolve 為 true 的 Promise;沒找到 Cache 物件,則返回 false。
keys
語法:
caches.keys().then(keyList => {
//對 keyList 做操作
});
複製程式碼
返回 Promise。包含 CacheStorage 下所有的 Cache 物件名稱字串的陣列。
has
語法:
caches.has(cacheName).then(boolean => {
// true: cacheName 快取存在
});
複製程式碼
返回一個 Promise 物件,快取存在時 resolve 的布林值為 true 否則為 false。
match
語法:
caches.match(request[, options]).then(response => {
// response 操作
// 如果未匹配到,則 resolve 返回 undefined
});
複製程式碼
引數:
request: 要匹配的 Request。可以是 Request 物件或 URL 字串。
options:(可選) 可選。用於控制如何進行匹配操作。
- ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
- ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
- ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)
- cacheName:String。表示所要搜尋的快取名稱。如果不設定則全域性搜尋,查詢到第一個,立即返回。
Cache 方法
Cache 是 CacheStorage 的儲存實現,以 Request 做為 key,以 Response 做為 value 來進行儲存。可以通過 CacheStorage.open(cacheName)
開啟 Cache 來進行操作。
Cache 資料生成後,將會一直存在,修改/刪除 需要通過指令碼自己去實現。
注: Cache.add/Cache.put/Cache.addAll
只能在 request method 為 GET 的情況下使用。並且相同的 request key 下的 cache,在這三個方法下會被覆蓋。
put
通過指定的 request 和 response 新增到 cache 中。
response 的 status 可以是任意型別。
語法:
cache.put(request, response).then(() => {
// 將 request/response 鍵值對新增到cache中
});
複製程式碼
引數:
注: request 引數,method 只支援 GET,否則會出現「TypeError: Request method POST
is unsupported」 錯誤。
delete
刪除匹配 request key 的 cache ,找到並刪除成功 resolve(true)。
語法:
cache.delete(request,{options}).then(boolean => {
// true: 你的 cache 已經刪除
});
複製程式碼
引數:
request: 請求刪除的 Request。 options:(可選) 控制刪除搜尋 key 如何去匹配(同 match 方法)。
- ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
- ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
- ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)
add
給定 request 引數,自動請求獲取 response,並填入 cache 物件中。
等於 fetch + cache.put
。
注: response status 為 opaque 的不能通過 add 方法新增,返回 reject。
語法:
cache.add(request).then(() => {
// request 已經新增到 cache
});
複製程式碼
addAll
和 Cache.add 作用相同,引數為 request 的陣列。
注: 只有在所有 requests 都成功的情況下,才能完成 cache 快取。
語法:
cache.addAll(requests[]).then(() => {
// 所有 requests 都新增到 cache 。
});
複製程式碼
match
返回匹配 request key 的第一個 cache。
注: 即使沒有匹配到,也將返回 resolve,只是值為 undefined。
語法:
cache.match(request, {options}).then(response => {
// 對 response 做一些處理
});
複製程式碼
引數:
request: 請求匹配的 Request。 options:(可選) 控制搜尋 key 如何去匹配(同 match 方法)。
- ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
- ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
- ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)
matchAll
作用同 Cache.match,區別在於 Cache.match 返回匹配的 responses[0],而 Cache.matchAll 返回所有匹配的 responses 陣列。
語法:
cache.matchAll(request,{options}).then(responses => {
// 對 responses 陣列做一些處理
});
複製程式碼
引數:
request: 請求匹配的 Request。 options:(可選) 控制搜尋 key 如何去匹配(同 match 方法)。
- ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
- ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
- ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)
keys
返回當前 cache 例項下所有的 key。
注: 具有相同URL但不同請求頭的請求,如果它們的響應頭中有 VARY 頭部,則他們可以被返回。
語法:
cache.keys(request,{options}).then(keys => {
// 對 requests 做一些處理
});
複製程式碼
引數:
request:(可選) 如果一個相關 Request key 被指定,則返回對應的 Request。 options:(可選) 控制搜尋 key 如何去匹配(同 match 方法)。
- ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
- ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
- ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)
除錯檢視
可以通過 Chrome 的 DevTools進行檢視。
Application → Cache → Cache Storage
Cache Storage 中是以 caches.open 建立的 cacheName 的 cache,右側列表中是通過 put/add/addAll 新增的 request key,點選可以在下方檢視相關的 request 和 response。
快取空間問題
web 端的離線儲存方式有三種,分別是:
- Temporary
- Persistent
- Unlimited
而 Cache 屬於 Temporary 型別。Temporary 儲存是一種臨時儲存,任何Web應用程式都可以在沒有前期配額請求或使用者提示的情況下使用,但儲存的資料可以被瀏覽器隨時刪掉(佔用空間過多時,自動清理)。可以類比於檔案系統的 / tmp 目錄。
在 Chrome 和 Opera 中可以使用新的實驗性 API 向裝置請求持久化存取許可權:
navigator.storage.persist().then(isGranted => {
// true : 授權
})
複製程式碼
各瀏覽器的離線空間:
- Chrome <6% of free space
- Firefox <10% of free space
- Safari <50MB
- IE10 <250MB
- Edge Depenent on volume size
溢位處理策略:
- Chrome LRU once Chrome runs out of sapce
- Firefox LRU if the whole disk gets full
- Safari No eviction
- Edge No eviction
實際資料可通過 Quota Management API 來檢視。
常見問題
跨域快取
對於跨域快取,跨域的資源需要開啟 Access-Control-Allow-Origin
頭部,並且 Request 的 mode 需要設定為 cors。
const req = new Request("https://cross.com", { mode: "cors" });
fetch(req).then(response => {
caches.open("cacheName").then(cache => {
cache.put(req, res);
});
});
複製程式碼
如果跨域資源沒有開啟 Access-Control-Allow-Origin
頭部,則需要把 mode 設定為 no-cors,但會導致 response 的 status 為 0 的 opaque 響應,後面會講。
POST 快取
因 Cache 中的規範指出只能儲存 GET 型別的 Request,所以想快取 POST 型別的 Request 怎麼辦呢?
這裡建議兩種方式:
- 將 Response 進行序列化,或者將實際資料提取,放到 IndexDB 中進行儲存,監聽 fetch 事件的時候,匹配 IndexDB 來查詢響應,並生成 Response 例項。
- 一些特殊的 POST 請求,如內容都是固化好的引數,通常不會發生變化,並且 URL 中有唯一性的標誌,則可以在 fetch 中監聽到這種特殊 POST 時,把其轉化為 GET 的 Request 來做為 cache 的 key,等 Response 收到後,把 POST 的 Response 做為轉化出來的 GET key 的 value 來儲存,即可實現。
opaque 不透明響應快取
針對 response 的 status 為 0 的 opaque 響應資源,也是可以儲存到 cache 中的,但是並不建議儲存。因為響應狀態是 0,並不能確認資源是否完整及正確,快取下來後,在 cache 中也是無法檢視其長度大小的,並且會導致一些儲存問題。
並且,突然間 Cache Storage 變成了 10.3MB。 但儲存的這個 opaque 狀態的響應實際只有幾 kb,但快取起來卻達到好幾 MB 這是什麼原因呢?其實這是 Chrome 瀏覽器層為防止出現安全問題,所以把所有 opaque 狀態的請求都以這種幾 MB 的方式進行填充來保證安全的。
所以針對 opaque 狀態的請求,建議:
- 不進行 opaque 狀態型別的快取。
- 對 opaque 狀態類弄的快取新增
Access-Control-Allow-Origin
頭來實現狀態透明。
瀏覽器相容性
部落格名稱:王樂平部落格
CSDN部落格地址:blog.csdn.net/lecepin