日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
瀏覽器緩存庫設(shè)計總結(jié)(localStorage/indexedDB)

前言

瀏覽器緩存設(shè)計一直是web性能優(yōu)化中非常重要的一個環(huán)節(jié),也是SPA應(yīng)用盛行的今天不得不考慮的問題.作為一名優(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ù)解決您的所有建站問題。

每個Web應(yīng)用體驗(yàn)都必須快速,對于漸進(jìn)式 Web 應(yīng)用更是如此。快速是指在屏幕上獲取有意義內(nèi)容所需的時間,要在不到 5 秒的時間內(nèi)提供交互式體驗(yàn)。并且,它必須真的很快。很難形容可靠的高性能有多重要??梢赃@樣想: 本機(jī)應(yīng)用的首次加載令人沮喪。已安裝的漸進(jìn)式 Web 應(yīng)用必須能讓用戶獲得可靠的性能。

本文會介紹一些筆者曾經(jīng)做過的Web性能優(yōu)化方案以及瀏覽器緩存的基本流程,并會著重介紹如何利用瀏覽器緩存API封裝適合自己團(tuán)隊的前端緩存庫來極大地提高應(yīng)用性能,并為公司省錢。

你將收獲

  • 熟悉瀏覽器緩存的基本過程。
  • Web性能優(yōu)化基本方案以及緩存策略為公司帶來的價值。
  • 基于localStorage的緩存方案設(shè)計以及庫的封裝(vuex/redux數(shù)據(jù)持久化解決方案)。
  • 基于indexedDB的緩存方案設(shè)計以及庫的封裝。
  • 結(jié)合http請求庫(axios/umi-request)進(jìn)行更細(xì)粒度的緩存代理層設(shè)計。

正文

一、瀏覽器緩存的基本過程

首先要想設(shè)計一個優(yōu)秀的緩存策略,一定要了解瀏覽器緩存的流程,接下來是筆者總結(jié)的一個基本的流程圖:

上圖展示了一個基本的從瀏覽器請求到展示資源的過程,我們的緩存策略一部分可以從以上流程出發(fā)來做優(yōu)化.我們都知道頁面的緩存狀態(tài)是由header決定的,下面具體介紹幾個概念:

1、 ETag

由服務(wù)端根據(jù)資源內(nèi)容生成一段 hash 字符串,標(biāo)識資源的狀態(tài),用戶第一次請求時服務(wù)器會將ETag隨著資源一起返回給瀏覽器, 再次請求時瀏覽器會將這串字符串傳回服務(wù)器,驗(yàn)證資源是否已經(jīng)修改,如果沒有修改直接使用緩存.具體流程可以是如下情景:

基于內(nèi)容的hash往往會比Last-modified更準(zhǔn)確。

2、 Last-modified

服務(wù)器端資源最后的修改時間,必須和 cache-control 共同使用,是檢查服務(wù)器端資源是否更新的一種方式。當(dāng)瀏覽器再次進(jìn)行請求時,會向服務(wù)器傳送 If-Modified-Since 報頭,詢問 Last-Modified 時間點(diǎn)之后資源是否被修改過。如果沒有修改,則返回 304,使用緩存;如果修改過,則再次去服務(wù)器請求資源,返回200,重新請求資源。

3、 Expires

緩存過期時間,用來指定資源到期的時間,是服務(wù)器端的具體的時間點(diǎn)。也就是說,Expires=max-age + 請求時間,需要和 Last-modified 結(jié)合使用. Expires 是 Web 服務(wù)器響應(yīng)消息頭字段,在響應(yīng) http 請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數(shù)據(jù),而無需再次請求。

4、 Cache-Control的max-age

單位為秒,指定設(shè)置緩存最大的有效時間。當(dāng)瀏覽器向服務(wù)器發(fā)送請求后,在 max-age 這段時間里瀏覽器就不會再向服務(wù)器發(fā)送請求了。以上就是瀏覽器緩存幾個基本的概念,更多知識可以在wiki中學(xué)習(xí),這里就不一一介紹了.接下來我們具體看看如何優(yōu)化web應(yīng)用以及緩存策略給公司帶來的價值。

二、Web性能優(yōu)化基本方案以及緩存策略為公司帶來的價值

Web性能優(yōu)化又是老生常談的問題了,幾年前就一直在探討這個問題,筆者大致盤點(diǎn)一下性能優(yōu)化的幾個常用的方向:

1、資源的合并與壓縮

比如我們常用的gulp或者webpack這些打包工具, 可以幫我們壓縮js,css,html代碼,并且將不同頁面模塊的js,css打包合并到一個文件中,好處就是減少了http請求,降低了資源的體積,使得響應(yīng)更快.但是仍然存在一個缺陷,就是合并代碼會導(dǎo)致一次請求的資源體積會比之前分包的要大,所以會一定程度的影響頁面渲染時間,所以這里需要做一個權(quán)衡,或者部分采用按需加載的方式。

2、圖片壓縮

一個網(wǎng)站往往更占資源的是媒體文件,比如圖片,視頻,音頻等,對于圖片在發(fā)布到線上時最好是需求提前壓縮一下, 為了減少圖片請求幾年前常用的做法是雪碧圖,也就是幾張圖片合成一張大圖,通過背景定位來顯示不同的圖片,不過目前貌似用的不多了,現(xiàn)在更多的采用字體圖標(biāo),svg,或者webp,所以我們需要根據(jù)不同的場景使用不同的策略,當(dāng)然目前主流的云平臺支持對象存儲,對媒體資源有不錯的優(yōu)化,有條件的可以采用這種方案,比如七牛云,阿里的對象存儲oss。

3、 合理規(guī)劃html代碼結(jié)構(gòu)

這個優(yōu)化主要是為了提高頁面渲染時間,我們都知道css和js的加載一般都是阻塞的, css不會阻塞js和外部腳本的加載,但是會阻塞js的執(zhí)行, 如果我們把css放到body最底部,那么我們在網(wǎng)絡(luò)不好的情況下可能會看到先展示html文本然后才渲染頁面樣式的窘境,如果我們把js腳本放到head內(nèi),那么將會阻塞后面內(nèi)容的渲染,并且造成一些應(yīng)dom還未生成的導(dǎo)致的錯誤, 雖然我們可以采用async、defer讓script變成異步的,但是如果不同js文件有依賴關(guān)系,那么很可能導(dǎo)致意外的錯誤,所以我們的最佳實(shí)踐往往是如下這種結(jié)構(gòu)的:



趣談前端






...

// html內(nèi)容



4、資源的懶加載和預(yù)加載

資源的懶加載可以極大的降低頁面首屏?xí)r間, 我們不僅僅可以對圖片采用懶加載, 即只給用戶展示可視區(qū)域內(nèi)的圖片(雖然圖片的懶加載意義更加重大),我們還可以對內(nèi)容進(jìn)行懶加載,本質(zhì)上是一種特殊的分頁技巧, jquery時代的lazyload是一個很好的例子,當(dāng)然現(xiàn)在自己實(shí)現(xiàn)一個懶加載方案也非常簡單,我們只需要使用getBoundingClientRect這個API配合具體業(yè)務(wù)使用即可,內(nèi)容型平臺用的比較多,比如我們手機(jī)滑到某一區(qū)域才加載更多內(nèi)容,筆者之前做的某頭條的廣告埋點(diǎn)上報機(jī)制就是一個很好的例子.大致思路如下:

預(yù)加載就是提前加載圖片,當(dāng)用戶需要查看時可直接從本地緩存中渲染.這種機(jī)制和懶加載往往相反,預(yù)加載為了帶來更加流暢的用戶體驗(yàn),比如漫畫網(wǎng)站,我們?nèi)绻皇褂妙A(yù)加載,那么用戶頻繁切換圖片時體驗(yàn)是相當(dāng)差的,所以我們需要提前將圖片加載好,犧牲的代價就是用戶可能會等待一定的時間來開啟"漫畫之旅"。

5、靜態(tài)資源使用cdn

cdn的好處就是可以突破瀏覽器同域名下一次最大請求并發(fā)數(shù)量,從而不用"排隊"來提高加載速度.我們都是到同一域名下瀏覽器最多并發(fā)請求6條(不同瀏覽器之間有差異),超過6條的則會等待前面的請求完成才會繼續(xù)發(fā)起,如果使用cdn,一方面它采用離用戶最近的資源來響應(yīng),另一方面cdn往往和應(yīng)用處于不同的域下,所以可以不用等待其他域下的并發(fā)數(shù)限制,從而加速網(wǎng)站響應(yīng)。

6、瀏覽器緩存

這一塊就是本文上一節(jié)中探討的內(nèi)容,這里不做過多介紹了,我們還可以采用localStorage, indexedDB來進(jìn)一步優(yōu)化緩存,我們下面會詳細(xì)介紹這一塊的內(nèi)容。

7、代碼層面的優(yōu)化

代碼層面往往就是工程師自己對代碼掌控的能力,一個優(yōu)秀的工程師往往會寫出代碼量更少,性能更好的代碼, 比如采用函數(shù)式編程來優(yōu)化代碼結(jié)構(gòu),使用算法來提高js代碼執(zhí)行效率(比如排序,搜索算法),如果想了解更多這方面的知識,可以參考筆者之前寫的兩篇文章:

  • js基本搜索算法實(shí)現(xiàn)與170萬條數(shù)據(jù)下的性能測試。
  • 《前端算法系列》如何讓前端代碼速度提高60倍。

所以說在寫代碼時,請無時無都都提醒自己, 今天的代碼跑性能測試了嗎?

8、使用web worker技術(shù)并行執(zhí)行js代碼,減少阻塞

Web Worker的作用就是為 JavaScript 創(chuàng)造多線程環(huán)境,允許主線程創(chuàng)建 Worker 線程,將一些任務(wù)分配給后者運(yùn)行。在主線程運(yùn)行的同時,Worker 線程在后臺運(yùn)行,兩者互不干擾。等到 Worker 線程完成計算任務(wù),再把結(jié)果返回給主線程。這樣的好處是,一些計算密集型或高延遲的任務(wù),被 Worker 線程負(fù)擔(dān)了,主線程(通常負(fù)責(zé) UI 交互)就會很流暢,不會被阻塞或拖慢。

Worker 線程一旦新建成功,就會始終運(yùn)行,不會被主線程上的活動(比如用戶點(diǎn)擊按鈕、提交表單)打斷。這樣有利于隨時響應(yīng)主線程的通信。但是Worker比較耗費(fèi)資源,一旦使用完畢,就應(yīng)該關(guān)閉。

知道了這些web性能優(yōu)化知識,我們還要充分理解為什么要做這些優(yōu)化.有過內(nèi)容平臺開發(fā)經(jīng)驗(yàn)的朋友可能會知道,內(nèi)容平臺比較耗資源的就是媒體資源,比如圖片,視頻等,我們?yōu)榱擞懈玫挠脩趔w驗(yàn)往往會將這些資源放到第三方服務(wù)平臺存儲,這樣會有更好的請求性能還不用擔(dān)心服務(wù)器壓力,但是唯一缺點(diǎn)就是燒錢.每一個請求都是錢,雖然不多, 但是也抗不了百萬千萬的ip請求量,所以這些做的好的內(nèi)容平臺每年至少在這塊花個幾百萬很正常,尤其是按請求付費(fèi).所以優(yōu)化好了網(wǎng)站, 一方面可以帶來更多的用戶,更好的用戶體驗(yàn),也可以幫公司省流量, 進(jìn)而幫老板省錢!(跪求求一個年終獎o(╥﹏╥)o)。

接下里的內(nèi)容,就教大家如何省錢。

三、基于localStorage的緩存方案設(shè)計以及庫的封裝(vuex/redux數(shù)據(jù)持久化解決方案)

localStorage屬性允許你訪問一個Document 源(origin)的對象 Storage;存儲的數(shù)據(jù)將保存在瀏覽器會話中。localStorage 類似 sessionStorage,但其區(qū)別在于:存儲在 localStorage 的數(shù)據(jù)可以長期保留;而當(dāng)頁面會話結(jié)束——也就是說,當(dāng)頁面被關(guān)閉時,存儲在 sessionStorage 的數(shù)據(jù)會被清除 。

關(guān)于localStorage的文章也寫了很多,使用方法也很簡單, 這里就不做過多介紹了,但是有沒有考慮自己封裝一個localStorage呢? 大多數(shù)人可能會覺得很多余,因?yàn)閘ocalStorage提供的api已經(jīng)夠簡單了,沒必要封裝,但是你有沒有考慮過,localStorage是持久化緩存,不支持過期時間,所以有些業(yè)務(wù)場景下原生localStorage是滿足不了的,所以這種情況下餓哦們需要自己實(shí)現(xiàn)具有過期時間的localStorage庫, 關(guān)于如何實(shí)現(xiàn)該功能,筆者之前也寫過一篇文章,有詳細(xì)的介紹,并且可以讓localStorage使用起來更強(qiáng)大,感興趣的可以學(xué)習(xí)研究一下:

  • 基于 localStorage 實(shí)現(xiàn)一個具有過期時間的 DAO 庫。

筆者已經(jīng)將庫發(fā)布到npm上了,可以通過如下方式安裝使用:

import dao from @alex_xu/dao

或者在html標(biāo)簽中直接使用umd文件,github地址: 基于localStorage封裝的可以設(shè)置過期時間的庫。

我們常用的vue里的狀態(tài)管理庫vuex,因?yàn)闋顟B(tài)都是存在內(nèi)存中的,那么如果要做web離線應(yīng)用,或者web游戲,我們往往需要考慮持久化緩存, 那么我們也可以借助localStorage來實(shí)現(xiàn)狀態(tài)的持久化功能,但是請記住,localStorage的存儲空間在5-10M,如果有更大的需求,可以采用接下來介紹的indexedDB來實(shí)現(xiàn)。

四、基于indexedDB的緩存方案設(shè)計以及庫的封裝

IndexedDB主要用于客戶端存儲大量結(jié)構(gòu)化數(shù)據(jù)(包括, 文件/ blobs)。該API使用索引來實(shí)現(xiàn)對該數(shù)據(jù)的高性能搜索。雖然 Web Storage 對于存儲較少量的數(shù)據(jù)很有用,但對于存儲更大量的結(jié)構(gòu)化數(shù)據(jù)來說,這種方法不太有用。IndexedDB是一個事務(wù)型數(shù)據(jù)庫系統(tǒng),類似于基于SQL的RDBMS。然而,不像RDBMS使用固定列表,IndexedDB是一個基于JavaScript的面向?qū)ο蟮臄?shù)據(jù)庫。它允許我們存儲和檢索用鍵索引的對象;可以存儲結(jié)構(gòu)化克隆算法支持的任何對象。我們只需要指定數(shù)據(jù)庫模式,打開與數(shù)據(jù)庫的連接,然后檢索和更新一系列事務(wù)。

我們剛剛接觸indexedDB時往往覺得它很難懂, 我們首先需要使用open方法打開數(shù)據(jù)庫,因?yàn)閕ndexedDB大部分方法都是異步的,所以我們很難管理, 包括創(chuàng)建事務(wù),創(chuàng)建表(一組數(shù)據(jù)的對象存儲區(qū)), 添加對象存儲等,這里筆者不會介紹如何使用indexedDB的具體使用方法,而是叫大家如何簡化操作indexedDB的使用流程,封裝成一個簡單好用的緩存庫.以下的封裝都是基于promise,這樣使用起來更優(yōu)雅.以下是封裝的思路:

我們工作中處理的indexedDB無非如上幾個操作,所以我們需要將其從indexedDB底層API中抽離出來這幾個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
// 打開/創(chuàng)建數(shù)據(jù)庫
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
}
}
// 訪問表中數(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) {
// 判斷緩存是否過期
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})
}
}
})
}
// 遍歷訪問表中所有數(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)了一個基于promise的且支持過期時間的indexedDB庫,實(shí)現(xiàn)過期時間也非常簡單,就是在創(chuàng)建表的行時在底層添加一個過期時間字段,用戶需要設(shè)置改行過期時間時, 只需要添加過期時間即可,當(dāng)我們再次獲取表格數(shù)據(jù)時只需要檢測改行是否過期,如果過期就清除重新設(shè)置即可。

五、結(jié)合http請求庫(axios/umi-request)進(jìn)行更細(xì)粒度的緩存代理層設(shè)計

為了更大程度的發(fā)揮indexedDB存儲空間的優(yōu)勢,并且進(jìn)一步優(yōu)化緩存策略,我們來可以做緩存攔截.我們都知道,一個應(yīng)用的有些請求不需要頻繁獲取,比如省市級聯(lián)數(shù)據(jù), 區(qū)位地圖數(shù)據(jù),或者一些不需要經(jīng)常更新的數(shù)據(jù), 如果我們可以做到只請求一次, 下次請求直接使用內(nèi)存數(shù)據(jù),并設(shè)置一個過期時間, 到過期時間之后會重新請求數(shù)據(jù), 那么是不是對請求又可以做一次優(yōu)化?我們第一印象可能會寫出這樣的代碼:

if(!store.get('xx')){
http.get('xxx').then(res => {
res && store.set('xx', res, 12 * 60 * 60 * 1000)
})
}

這樣雖然可以實(shí)現(xiàn)功能,但是每一個業(yè)務(wù)都要寫類似的代碼, 往往很難受, 所以作為一個有追求的程序員,我們可以在請求上下功夫.我們都有過axios或者fetch庫的使用經(jīng)驗(yàn),我們也接觸過請求/響應(yīng)攔截器的使用, 那么我們能不能考慮對請求本身也做一層攔截呢?我想實(shí)現(xiàn)的效果是我們在業(yè)務(wù)里還是正常的像之前一樣使用請求,比如:

req.get('/getName?type=xxx').then(res)

然而內(nèi)部已經(jīng)幫我們做好請求緩存了,我們的req實(shí)際上不是axios或者fetch的實(shí)例,而是一層代理。

通過這種方式我們對原來的請求方式可以不做任何改變, 完全采用代理機(jī)制在請求攔截器中和響應(yīng)攔截器中布局我們的代理即可,關(guān)鍵點(diǎn)就是存到數(shù)據(jù)庫中的內(nèi)容要和服務(wù)器響應(yīng)的內(nèi)容結(jié)構(gòu)一致。

以上方式我們可以對所有的get請求做緩存,如果我們只想對部分請求做緩存,其實(shí)利用以上機(jī)制實(shí)現(xiàn)也很簡單,我們只需要設(shè)置緩存白名單, 在請求攔截器中判斷如果在白名單內(nèi)才走緩存邏輯即可。

這樣,我們再次進(jìn)行某項數(shù)據(jù)的搜索時,可以不走任何http請求,直接從indexedDB中獲取,這樣可以為公司節(jié)省大量的流量。

關(guān)于indexedDB的庫的封裝,我也發(fā)布到npm和github上了,大家可以直接使用或者進(jìn)行二次開發(fā)。

  • github地址: xdb-采用promise封裝的indexedDB存儲庫。

網(wǎng)頁名稱:瀏覽器緩存庫設(shè)計總結(jié)(localStorage/indexedDB)
網(wǎng)址分享:http://m.5511xx.com/article/cdccohd.html