新聞中心
前言
瀏覽器緩存設(shè)計(jì)一直是web性能優(yōu)化中非常重要的一個(gè)環(huán)節(jié),也是SPA應(yīng)用盛行的今天不得不考慮的問(wèn)題.作為一名優(yōu)秀的前端工程師,為了讓我們的應(yīng)用更流暢,用戶體驗(yàn)更好,我們有必要做好瀏覽器緩存策略。

創(chuàng)新互聯(lián)公司成立于2013年,先為青神等服務(wù)建站,青神等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為青神企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。
每個(gè)Web應(yīng)用體驗(yàn)都必須快速,對(duì)于漸進(jìn)式 Web 應(yīng)用更是如此??焖偈侵冈谄聊簧汐@取有意義內(nèi)容所需的時(shí)間,要在不到 5 秒的時(shí)間內(nèi)提供交互式體驗(yàn)。并且,它必須真的很快。很難形容可靠的高性能有多重要。可以這樣想: 本機(jī)應(yīng)用的首次加載令人沮喪。已安裝的漸進(jìn)式 Web 應(yīng)用必須能讓用戶獲得可靠的性能。
本文會(huì)介紹一些筆者曾經(jīng)做過(guò)的Web性能優(yōu)化方案以及瀏覽器緩存的基本流程,并會(huì)著重介紹如何利用瀏覽器緩存API封裝適合自己團(tuán)隊(duì)的前端緩存庫(kù)來(lái)極大地提高應(yīng)用性能,并為公司省錢。
你將收獲
- 熟悉瀏覽器緩存的基本過(guò)程。
- Web性能優(yōu)化基本方案以及緩存策略為公司帶來(lái)的價(jià)值。
- 基于localStorage的緩存方案設(shè)計(jì)以及庫(kù)的封裝(vuex/redux數(shù)據(jù)持久化解決方案)。
- 基于indexedDB的緩存方案設(shè)計(jì)以及庫(kù)的封裝。
- 結(jié)合http請(qǐng)求庫(kù)(axios/umi-request)進(jìn)行更細(xì)粒度的緩存代理層設(shè)計(jì)。
正文
一、瀏覽器緩存的基本過(guò)程
首先要想設(shè)計(jì)一個(gè)優(yōu)秀的緩存策略,一定要了解瀏覽器緩存的流程,接下來(lái)是筆者總結(jié)的一個(gè)基本的流程圖:
上圖展示了一個(gè)基本的從瀏覽器請(qǐng)求到展示資源的過(guò)程,我們的緩存策略一部分可以從以上流程出發(fā)來(lái)做優(yōu)化.我們都知道頁(yè)面的緩存狀態(tài)是由header決定的,下面具體介紹幾個(gè)概念:
1、 ETag
由服務(wù)端根據(jù)資源內(nèi)容生成一段 hash 字符串,標(biāo)識(shí)資源的狀態(tài),用戶第一次請(qǐng)求時(shí)服務(wù)器會(huì)將ETag隨著資源一起返回給瀏覽器, 再次請(qǐng)求時(shí)瀏覽器會(huì)將這串字符串傳回服務(wù)器,驗(yàn)證資源是否已經(jīng)修改,如果沒(méi)有修改直接使用緩存.具體流程可以是如下情景:
基于內(nèi)容的hash往往會(huì)比Last-modified更準(zhǔn)確。
2、 Last-modified
服務(wù)器端資源最后的修改時(shí)間,必須和 cache-control 共同使用,是檢查服務(wù)器端資源是否更新的一種方式。當(dāng)瀏覽器再次進(jìn)行請(qǐng)求時(shí),會(huì)向服務(wù)器傳送 If-Modified-Since 報(bào)頭,詢問(wèn) Last-Modified 時(shí)間點(diǎn)之后資源是否被修改過(guò)。如果沒(méi)有修改,則返回 304,使用緩存;如果修改過(guò),則再次去服務(wù)器請(qǐng)求資源,返回200,重新請(qǐng)求資源。
3、 Expires
緩存過(guò)期時(shí)間,用來(lái)指定資源到期的時(shí)間,是服務(wù)器端的具體的時(shí)間點(diǎn)。也就是說(shuō),Expires=max-age + 請(qǐng)求時(shí)間,需要和 Last-modified 結(jié)合使用. Expires 是 Web 服務(wù)器響應(yīng)消息頭字段,在響應(yīng) http 請(qǐng)求時(shí)告訴瀏覽器在過(guò)期時(shí)間前瀏覽器可以直接從瀏覽器緩存取數(shù)據(jù),而無(wú)需再次請(qǐng)求。
4、 Cache-Control的max-age
單位為秒,指定設(shè)置緩存最大的有效時(shí)間。當(dāng)瀏覽器向服務(wù)器發(fā)送請(qǐng)求后,在 max-age 這段時(shí)間里瀏覽器就不會(huì)再向服務(wù)器發(fā)送請(qǐng)求了。以上就是瀏覽器緩存幾個(gè)基本的概念,更多知識(shí)可以在wiki中學(xué)習(xí),這里就不一一介紹了.接下來(lái)我們具體看看如何優(yōu)化web應(yīng)用以及緩存策略給公司帶來(lái)的價(jià)值。
二、Web性能優(yōu)化基本方案以及緩存策略為公司帶來(lái)的價(jià)值
Web性能優(yōu)化又是老生常談的問(wèn)題了,幾年前就一直在探討這個(gè)問(wèn)題,筆者大致盤點(diǎn)一下性能優(yōu)化的幾個(gè)常用的方向:
1、資源的合并與壓縮
比如我們常用的gulp或者webpack這些打包工具, 可以幫我們壓縮js,css,html代碼,并且將不同頁(yè)面模塊的js,css打包合并到一個(gè)文件中,好處就是減少了http請(qǐng)求,降低了資源的體積,使得響應(yīng)更快.但是仍然存在一個(gè)缺陷,就是合并代碼會(huì)導(dǎo)致一次請(qǐng)求的資源體積會(huì)比之前分包的要大,所以會(huì)一定程度的影響頁(yè)面渲染時(shí)間,所以這里需要做一個(gè)權(quán)衡,或者部分采用按需加載的方式。
2、圖片壓縮
一個(gè)網(wǎng)站往往更占資源的是媒體文件,比如圖片,視頻,音頻等,對(duì)于圖片在發(fā)布到線上時(shí)最好是需求提前壓縮一下, 為了減少圖片請(qǐng)求幾年前常用的做法是雪碧圖,也就是幾張圖片合成一張大圖,通過(guò)背景定位來(lái)顯示不同的圖片,不過(guò)目前貌似用的不多了,現(xiàn)在更多的采用字體圖標(biāo),svg,或者webp,所以我們需要根據(jù)不同的場(chǎng)景使用不同的策略,當(dāng)然目前主流的云平臺(tái)支持對(duì)象存儲(chǔ),對(duì)媒體資源有不錯(cuò)的優(yōu)化,有條件的可以采用這種方案,比如七牛云,阿里的對(duì)象存儲(chǔ)oss。
3、 合理規(guī)劃html代碼結(jié)構(gòu)
這個(gè)優(yōu)化主要是為了提高頁(yè)面渲染時(shí)間,我們都知道css和js的加載一般都是阻塞的, css不會(huì)阻塞js和外部腳本的加載,但是會(huì)阻塞js的執(zhí)行, 如果我們把css放到body最底部,那么我們?cè)诰W(wǎng)絡(luò)不好的情況下可能會(huì)看到先展示html文本然后才渲染頁(yè)面樣式的窘境,如果我們把js腳本放到head內(nèi),那么將會(huì)阻塞后面內(nèi)容的渲染,并且造成一些應(yīng)dom還未生成的導(dǎo)致的錯(cuò)誤, 雖然我們可以采用async、defer讓script變成異步的,但是如果不同js文件有依賴關(guān)系,那么很可能導(dǎo)致意外的錯(cuò)誤,所以我們的最佳實(shí)踐往往是如下這種結(jié)構(gòu)的:
趣談前端
...
// html內(nèi)容
4、資源的懶加載和預(yù)加載
資源的懶加載可以極大的降低頁(yè)面首屏?xí)r間, 我們不僅僅可以對(duì)圖片采用懶加載, 即只給用戶展示可視區(qū)域內(nèi)的圖片(雖然圖片的懶加載意義更加重大),我們還可以對(duì)內(nèi)容進(jìn)行懶加載,本質(zhì)上是一種特殊的分頁(yè)技巧, jquery時(shí)代的lazyload是一個(gè)很好的例子,當(dāng)然現(xiàn)在自己實(shí)現(xiàn)一個(gè)懶加載方案也非常簡(jiǎn)單,我們只需要使用getBoundingClientRect這個(gè)API配合具體業(yè)務(wù)使用即可,內(nèi)容型平臺(tái)用的比較多,比如我們手機(jī)滑到某一區(qū)域才加載更多內(nèi)容,筆者之前做的某頭條的廣告埋點(diǎn)上報(bào)機(jī)制就是一個(gè)很好的例子.大致思路如下:
預(yù)加載就是提前加載圖片,當(dāng)用戶需要查看時(shí)可直接從本地緩存中渲染.這種機(jī)制和懶加載往往相反,預(yù)加載為了帶來(lái)更加流暢的用戶體驗(yàn),比如漫畫網(wǎng)站,我們?nèi)绻皇褂妙A(yù)加載,那么用戶頻繁切換圖片時(shí)體驗(yàn)是相當(dāng)差的,所以我們需要提前將圖片加載好,犧牲的代價(jià)就是用戶可能會(huì)等待一定的時(shí)間來(lái)開(kāi)啟"漫畫之旅"。
5、靜態(tài)資源使用cdn
cdn的好處就是可以突破瀏覽器同域名下一次最大請(qǐng)求并發(fā)數(shù)量,從而不用"排隊(duì)"來(lái)提高加載速度.我們都是到同一域名下瀏覽器最多并發(fā)請(qǐng)求6條(不同瀏覽器之間有差異),超過(guò)6條的則會(huì)等待前面的請(qǐng)求完成才會(huì)繼續(xù)發(fā)起,如果使用cdn,一方面它采用離用戶最近的資源來(lái)響應(yīng),另一方面cdn往往和應(yīng)用處于不同的域下,所以可以不用等待其他域下的并發(fā)數(shù)限制,從而加速網(wǎng)站響應(yīng)。
6、瀏覽器緩存
這一塊就是本文上一節(jié)中探討的內(nèi)容,這里不做過(guò)多介紹了,我們還可以采用localStorage, indexedDB來(lái)進(jìn)一步優(yōu)化緩存,我們下面會(huì)詳細(xì)介紹這一塊的內(nèi)容。
7、代碼層面的優(yōu)化
代碼層面往往就是工程師自己對(duì)代碼掌控的能力,一個(gè)優(yōu)秀的工程師往往會(huì)寫出代碼量更少,性能更好的代碼, 比如采用函數(shù)式編程來(lái)優(yōu)化代碼結(jié)構(gòu),使用算法來(lái)提高js代碼執(zhí)行效率(比如排序,搜索算法),如果想了解更多這方面的知識(shí),可以參考筆者之前寫的兩篇文章:
- js基本搜索算法實(shí)現(xiàn)與170萬(wàn)條數(shù)據(jù)下的性能測(cè)試。
- 《前端算法系列》如何讓前端代碼速度提高60倍。
所以說(shuō)在寫代碼時(shí),請(qǐng)無(wú)時(shí)無(wú)都都提醒自己, 今天的代碼跑性能測(cè)試了嗎?
8、使用web worker技術(shù)并行執(zhí)行js代碼,減少阻塞
Web Worker的作用就是為 JavaScript 創(chuàng)造多線程環(huán)境,允許主線程創(chuàng)建 Worker 線程,將一些任務(wù)分配給后者運(yùn)行。在主線程運(yùn)行的同時(shí),Worker 線程在后臺(tái)運(yùn)行,兩者互不干擾。等到 Worker 線程完成計(jì)算任務(wù),再把結(jié)果返回給主線程。這樣的好處是,一些計(jì)算密集型或高延遲的任務(wù),被 Worker 線程負(fù)擔(dān)了,主線程(通常負(fù)責(zé) UI 交互)就會(huì)很流暢,不會(huì)被阻塞或拖慢。
Worker 線程一旦新建成功,就會(huì)始終運(yùn)行,不會(huì)被主線程上的活動(dòng)(比如用戶點(diǎn)擊按鈕、提交表單)打斷。這樣有利于隨時(shí)響應(yīng)主線程的通信。但是Worker比較耗費(fèi)資源,一旦使用完畢,就應(yīng)該關(guān)閉。
知道了這些web性能優(yōu)化知識(shí),我們還要充分理解為什么要做這些優(yōu)化.有過(guò)內(nèi)容平臺(tái)開(kāi)發(fā)經(jīng)驗(yàn)的朋友可能會(huì)知道,內(nèi)容平臺(tái)比較耗資源的就是媒體資源,比如圖片,視頻等,我們?yōu)榱擞懈玫挠脩趔w驗(yàn)往往會(huì)將這些資源放到第三方服務(wù)平臺(tái)存儲(chǔ),這樣會(huì)有更好的請(qǐng)求性能還不用擔(dān)心服務(wù)器壓力,但是唯一缺點(diǎn)就是燒錢.每一個(gè)請(qǐng)求都是錢,雖然不多, 但是也抗不了百萬(wàn)千萬(wàn)的ip請(qǐng)求量,所以這些做的好的內(nèi)容平臺(tái)每年至少在這塊花個(gè)幾百萬(wàn)很正常,尤其是按請(qǐng)求付費(fèi).所以優(yōu)化好了網(wǎng)站, 一方面可以帶來(lái)更多的用戶,更好的用戶體驗(yàn),也可以幫公司省流量, 進(jìn)而幫老板省錢!(跪求求一個(gè)年終獎(jiǎng)o(╥﹏╥)o)。
接下里的內(nèi)容,就教大家如何省錢。
三、基于localStorage的緩存方案設(shè)計(jì)以及庫(kù)的封裝(vuex/redux數(shù)據(jù)持久化解決方案)
localStorage屬性允許你訪問(wèn)一個(gè)Document 源(origin)的對(duì)象 Storage;存儲(chǔ)的數(shù)據(jù)將保存在瀏覽器會(huì)話中。localStorage 類似 sessionStorage,但其區(qū)別在于:存儲(chǔ)在 localStorage 的數(shù)據(jù)可以長(zhǎng)期保留;而當(dāng)頁(yè)面會(huì)話結(jié)束——也就是說(shuō),當(dāng)頁(yè)面被關(guān)閉時(shí),存儲(chǔ)在 sessionStorage 的數(shù)據(jù)會(huì)被清除 。
關(guān)于localStorage的文章也寫了很多,使用方法也很簡(jiǎn)單, 這里就不做過(guò)多介紹了,但是有沒(méi)有考慮自己封裝一個(gè)localStorage呢? 大多數(shù)人可能會(huì)覺(jué)得很多余,因?yàn)閘ocalStorage提供的api已經(jīng)夠簡(jiǎn)單了,沒(méi)必要封裝,但是你有沒(méi)有考慮過(guò),localStorage是持久化緩存,不支持過(guò)期時(shí)間,所以有些業(yè)務(wù)場(chǎng)景下原生localStorage是滿足不了的,所以這種情況下餓哦們需要自己實(shí)現(xiàn)具有過(guò)期時(shí)間的localStorage庫(kù), 關(guān)于如何實(shí)現(xiàn)該功能,筆者之前也寫過(guò)一篇文章,有詳細(xì)的介紹,并且可以讓localStorage使用起來(lái)更強(qiáng)大,感興趣的可以學(xué)習(xí)研究一下:
- 基于 localStorage 實(shí)現(xiàn)一個(gè)具有過(guò)期時(shí)間的 DAO 庫(kù)。
筆者已經(jīng)將庫(kù)發(fā)布到npm上了,可以通過(guò)如下方式安裝使用:
import dao from @alex_xu/dao
或者在html標(biāo)簽中直接使用umd文件,github地址: 基于localStorage封裝的可以設(shè)置過(guò)期時(shí)間的庫(kù)。
我們常用的vue里的狀態(tài)管理庫(kù)vuex,因?yàn)闋顟B(tài)都是存在內(nèi)存中的,那么如果要做web離線應(yīng)用,或者web游戲,我們往往需要考慮持久化緩存, 那么我們也可以借助localStorage來(lái)實(shí)現(xiàn)狀態(tài)的持久化功能,但是請(qǐng)記住,localStorage的存儲(chǔ)空間在5-10M,如果有更大的需求,可以采用接下來(lái)介紹的indexedDB來(lái)實(shí)現(xiàn)。
四、基于indexedDB的緩存方案設(shè)計(jì)以及庫(kù)的封裝
IndexedDB主要用于客戶端存儲(chǔ)大量結(jié)構(gòu)化數(shù)據(jù)(包括, 文件/ blobs)。該API使用索引來(lái)實(shí)現(xiàn)對(duì)該數(shù)據(jù)的高性能搜索。雖然 Web Storage 對(duì)于存儲(chǔ)較少量的數(shù)據(jù)很有用,但對(duì)于存儲(chǔ)更大量的結(jié)構(gòu)化數(shù)據(jù)來(lái)說(shuō),這種方法不太有用。IndexedDB是一個(gè)事務(wù)型數(shù)據(jù)庫(kù)系統(tǒng),類似于基于SQL的RDBMS。然而,不像RDBMS使用固定列表,IndexedDB是一個(gè)基于JavaScript的面向?qū)ο蟮臄?shù)據(jù)庫(kù)。它允許我們存儲(chǔ)和檢索用鍵索引的對(duì)象;可以存儲(chǔ)結(jié)構(gòu)化克隆算法支持的任何對(duì)象。我們只需要指定數(shù)據(jù)庫(kù)模式,打開(kāi)與數(shù)據(jù)庫(kù)的連接,然后檢索和更新一系列事務(wù)。
我們剛剛接觸indexedDB時(shí)往往覺(jué)得它很難懂, 我們首先需要使用open方法打開(kāi)數(shù)據(jù)庫(kù),因?yàn)閕ndexedDB大部分方法都是異步的,所以我們很難管理, 包括創(chuàng)建事務(wù),創(chuàng)建表(一組數(shù)據(jù)的對(duì)象存儲(chǔ)區(qū)), 添加對(duì)象存儲(chǔ)等,這里筆者不會(huì)介紹如何使用indexedDB的具體使用方法,而是叫大家如何簡(jiǎn)化操作indexedDB的使用流程,封裝成一個(gè)簡(jiǎn)單好用的緩存庫(kù).以下的封裝都是基于promise,這樣使用起來(lái)更優(yōu)雅.以下是封裝的思路:
我們工作中處理的indexedDB無(wú)非如上幾個(gè)操作,所以我們需要將其從indexedDB底層API中抽離出來(lái)這幾個(gè)api.具體實(shí)現(xiàn)如下:
declare global {
interface Window { xdb: any; }
}
const xdb = (() => {
let instance:any = null
let dbName = ''
let DB = function(args:any) {
const cfg = {
name: args.name || 'test',
version: args.version || 1,
onSuccess(e:Event) {
args.onSuccess && args.onSuccess(e)
},
onUpdate(e:Event) {
args.onUpdate && args.onUpdate(e)
},
onError(e:Event) {
args.onError && args.onError(e)
}
}
this.dbName = args.name
this.request = null
this.db = null
// 打開(kāi)/創(chuàng)建數(shù)據(jù)庫(kù)
this.init = function() {
if (!window.indexedDB) {
console.log('你的瀏覽器不支持該版本')
return
}
let _this = this
this.request = window.indexedDB.open(this.dbName, cfg.version)
this.request.onerror = function (event:Event) {
cfg.onError(event)
}
this.request.onsuccess = function (event:Event) {
_this.db = _this.request.result
cfg.onSuccess(event)
}
this.request.onupgradeneeded = function (event:any) {
_this.db = event.target.result
cfg.onUpdate(event)
}
}
this.init()
// 添加表
this.createTable = function(name:string, opts:any = {}) {
let objectStore:any
if (!this.db.objectStoreNames.contains(name)) {
opts = {
keyPath: opts.keyPath,
indexs: Array.isArray(opts.indexs) ? opts.indexs : []
}
// indexs = [{
// indexName: 'name',
// key: 'name',
// unique: true
// }]
objectStore = this.db.createObjectStore(name, { keyPath: opts.keyPath })
if(opts.length) {
opts.indexs.forEach((item:any) => {
objectStore.createIndex(item.indexName, item.key, { unique: item.unique })
})
}
return objectStore
}
}
// 訪問(wèn)表中數(shù)據(jù)
this.get = function(tableName:string, keyPathVal:any) {
let _this = this
return new Promise((resolve, reject) => {
let transaction = this.db.transaction([tableName])
let objectStore = transaction.objectStore(tableName)
let request = objectStore.get(keyPathVal)
request.onerror = function(event:Event) {
reject({status: 500, msg: '事務(wù)失敗', err: event})
}
request.onsuccess = function(event:Event) {
if (request.result) {
// 判斷緩存是否過(guò)期
if(request.result.ex < Date.now()) {
resolve({status: 200, data: null})
_this.del(tableName, keyPathVal)
}else {
resolve({status: 200, data: request.result})
}
} else {
resolve({status: 200, data: null})
}
}
})
}
// 遍歷訪問(wèn)表中所有數(shù)據(jù)
this.getAll = function(tableName:string) {
return new Promise((reslove, reject) => {
let objectStore = this.db.transaction(tableName).objectStore(tableName)
let result:any = []
objectStore.openCursor().onsuccess = function (event:any) {
let cursor = event.target.result
if (cursor) {
result.push(cursor.value)
cursor.continue()
} else {
reslove({status: 200, data: result})
}
}
objectStore.openCursor().onerror = function (event:Event) {
reject({status: 500, msg: '事務(wù)失敗', err: event})
}
})
}
// 從表中添加一條數(shù)據(jù)
this.add = function(tableName:string, row:any, ex:number) {
return new Promise((reslove, reject) => {
let request = this.db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.add(Object.assign(row, ex ? { ex: Date.now() + ex } : {}))
request.onsuccess = function (event:Event) {
reslove({status: 200, msg: '數(shù)據(jù)寫入成功'})
}
request.onerror = function (event:Event) {
reject({status: 500, msg: '數(shù)據(jù)寫入失敗', err: event})
}
})
}
// 更新表中的數(shù)據(jù)
this.update = function(tableName:string, row:any) {
return new Promise((reslove, reject) => {
let request = this.db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.put(row)
request.onsuccess = function (event:Event) {
reslove({status: 200, msg: '數(shù)據(jù)更新成功'})
}
request.onerror = function (event:Event) {
reject({status: 500, msg: '數(shù)據(jù)更新失敗', err: event})
}
})
}
// 刪除某條數(shù)據(jù)
this.del = function(tableName:string, keyPathVal:any) {
return new Promise((resolve, reject) => {
let request = this.db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.delete(keyPathVal)
request.onsuccess = function (event:Event) {
resolve({status: 200, msg: '數(shù)據(jù)刪除成功'})
}
request.onerror = function (event:Event) {
reject({status: 500, msg: '數(shù)據(jù)刪除失敗', err: event})
}
})
}
// 清空表數(shù)據(jù)
this.clear = function(tableName:string) {
return new Promise((resolve, reject) => {
let request = this.db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.clear()
request.onsuccess = function (event:Event) {
resolve({status: 200, msg: '數(shù)據(jù)表已清空'})
}
request.onerror = function (event:Event) {
reject({status: 500, msg: '數(shù)據(jù)表清空失敗', err: event})
}
})
}
}
return {
loadDB(args:any) {
if(instance === undefined || dbName !== args.name) {
instance = new (DB as any)(args)
}
return instance
}
}
})()
window.xdb = xdb
export default xdb
這樣就實(shí)現(xiàn)了一個(gè)基于promise的且支持過(guò)期時(shí)間的indexedDB庫(kù),實(shí)現(xiàn)過(guò)期時(shí)間也非常簡(jiǎn)單,就是在創(chuàng)建表的行時(shí)在底層添加一個(gè)過(guò)期時(shí)間字段,用戶需要設(shè)置改行過(guò)期時(shí)間時(shí), 只需要添加過(guò)期時(shí)間即可,當(dāng)我們?cè)俅潍@取表格數(shù)據(jù)時(shí)只需要檢測(cè)改行是否過(guò)期,如果過(guò)期就清除重新設(shè)置即可。
五、結(jié)合http請(qǐng)求庫(kù)(axios/umi-request)進(jìn)行更細(xì)粒度的緩存代理層設(shè)計(jì)
為了更大程度的發(fā)揮indexedDB存儲(chǔ)空間的優(yōu)勢(shì),并且進(jìn)一步優(yōu)化緩存策略,我們來(lái)可以做緩存攔截.我們都知道,一個(gè)應(yīng)用的有些請(qǐng)求不需要頻繁獲取,比如省市級(jí)聯(lián)數(shù)據(jù), 區(qū)位地圖數(shù)據(jù),或者一些不需要經(jīng)常更新的數(shù)據(jù), 如果我們可以做到只請(qǐng)求一次, 下次請(qǐng)求直接使用內(nèi)存數(shù)據(jù),并設(shè)置一個(gè)過(guò)期時(shí)間, 到過(guò)期時(shí)間之后會(huì)重新請(qǐng)求數(shù)據(jù), 那么是不是對(duì)請(qǐng)求又可以做一次優(yōu)化?我們第一印象可能會(huì)寫出這樣的代碼:
if(!store.get('xx')){
http.get('xxx').then(res => {
res && store.set('xx', res, 12 * 60 * 60 * 1000)
})
}
這樣雖然可以實(shí)現(xiàn)功能,但是每一個(gè)業(yè)務(wù)都要寫類似的代碼, 往往很難受, 所以作為一個(gè)有追求的程序員,我們可以在請(qǐng)求上下功夫.我們都有過(guò)axios或者fetch庫(kù)的使用經(jīng)驗(yàn),我們也接觸過(guò)請(qǐng)求/響應(yīng)攔截器的使用, 那么我們能不能考慮對(duì)請(qǐng)求本身也做一層攔截呢?我想實(shí)現(xiàn)的效果是我們?cè)跇I(yè)務(wù)里還是正常的像之前一樣使用請(qǐng)求,比如:
req.get('/getName?type=xxx').then(res)
然而內(nèi)部已經(jīng)幫我們做好請(qǐng)求緩存了,我們的req實(shí)際上不是axios或者fetch的實(shí)例,而是一層代理。
通過(guò)這種方式我們對(duì)原來(lái)的請(qǐng)求方式可以不做任何改變, 完全采用代理機(jī)制在請(qǐng)求攔截器中和響應(yīng)攔截器中布局我們的代理即可,關(guān)鍵點(diǎn)就是存到數(shù)據(jù)庫(kù)中的內(nèi)容要和服務(wù)器響應(yīng)的內(nèi)容結(jié)構(gòu)一致。
以上方式我們可以對(duì)所有的get請(qǐng)求做緩存,如果我們只想對(duì)部分請(qǐng)求做緩存,其實(shí)利用以上機(jī)制實(shí)現(xiàn)也很簡(jiǎn)單,我們只需要設(shè)置緩存白名單, 在請(qǐng)求攔截器中判斷如果在白名單內(nèi)才走緩存邏輯即可。
這樣,我們?cè)俅芜M(jìn)行某項(xiàng)數(shù)據(jù)的搜索時(shí),可以不走任何http請(qǐng)求,直接從indexedDB中獲取,這樣可以為公司節(jié)省大量的流量。
關(guān)于indexedDB的庫(kù)的封裝,我也發(fā)布到npm和github上了,大家可以直接使用或者進(jìn)行二次開(kāi)發(fā)。
- github地址: xdb-采用promise封裝的indexedDB存儲(chǔ)庫(kù)。
網(wǎng)站欄目:瀏覽器緩存庫(kù)設(shè)計(jì)總結(jié)(localStorage/indexedDB)
文章起源:http://fisionsoft.com.cn/article/cdccohd.html


咨詢
建站咨詢
