背景
接口連路很長導(dǎo)致返回數(shù)據(jù)很慢,頁面長時間loading空白,甚至超時報錯,導(dǎo)致整個站的用戶體驗很差。場景特點:
- 數(shù)據(jù)量比較大,
- 僅用于進入頁面的時候展示,替代空白的loading頁面或者后端接口報錯的展示數(shù)據(jù)
- 不要求兼容性
為什么要用緩存
- 減少網(wǎng)絡(luò)寬帶消耗,減輕擁塞
- 降低服務(wù)器的壓力
- 加快頁面的打開速度
- 如果遠程服務(wù)器故障造成遠程服務(wù)器無法響應(yīng),打開歷史版本可以提供用戶體驗
緩存分類
- 數(shù)據(jù)庫緩存
- 服務(wù)器緩存
- 代理服務(wù)器緩存
- CDN緩存
- 瀏覽器緩存
- HTTP header緩存機制
- 強緩存(cache-control expires)
- 協(xié)商緩存(Etag last-modified)
- 數(shù)據(jù)緩存
- cookie
- storage (Local Storage和Session Storage)
- indexDB
- web SQL
- 應(yīng)用緩存
- mainifest
- HTTP header緩存機制
因為數(shù)據(jù)庫緩存和服務(wù)器緩存不是前端的控制所以提一下,下面考慮瀏覽器緩存,針對場景更多的考慮數(shù)據(jù)緩存
數(shù)據(jù)緩存方式對比
| 方式 | 存儲的大小 | 兼容性 | 難易程度 | 影響 |
|---|---|---|---|---|
| cookie | 4k | 兼容 | 手動封裝,有難度 | 和http請求一起發(fā)送 |
| storage | 5M | ie8 | 簡單 | 不和http一起發(fā)送 |
| IndexedDB | 無限容量 | ie11,safari9.3,opera不支持 | 復(fù)雜 | 不和http一起發(fā)送;異步操作大量數(shù)據(jù)操作不會拖慢網(wǎng)頁 |
IndexedDB
名詞解釋
- 關(guān)系型數(shù)據(jù)庫:后端存儲數(shù)據(jù)用到的一般是關(guān)系型數(shù)據(jù)庫,例如存儲人員信息,根據(jù)人的id去關(guān)聯(lián)他的考勤,發(fā)布的文章等等。它對于一致性的要求非常嚴格
- 非關(guān)系型數(shù)據(jù)庫:數(shù)據(jù)結(jié)構(gòu)不固定,非常容易拓展,常見的有3個,1、數(shù)據(jù)結(jié)構(gòu)類似json的key:value,典型的是radius;2、以列來定義每一個表,每一行的內(nèi)容可以不同,茹茹第一行存人員信息,第二行存商品信息;3、mongodb,類似關(guān)系型數(shù)據(jù)庫的非關(guān)系型數(shù)據(jù)庫
- 游標:可以理解為卡尺,一行行的數(shù)據(jù)
- 鎖:數(shù)據(jù)庫中的鎖是用來保證數(shù)據(jù)庫高并發(fā)的時候數(shù)據(jù)一致性的一種機制,例如車票售出
- 事務(wù):對數(shù)據(jù)庫的操作,而且專指一個序列上的操作,例如銀行轉(zhuǎn)帳(轉(zhuǎn)出和轉(zhuǎn)入要么都執(zhí)行要么都不執(zhí)行)
基本概念
- 數(shù)據(jù)庫:IDBDatabase 對象
- 可以有任意多個數(shù)據(jù)庫
- 含有版本的概念,同時只能有一個版本的數(shù)據(jù)庫
- 對象倉庫:IDBObjectStore 對象
- 數(shù)據(jù)庫里面含有很多對象倉庫,類似表
- 索引:IDBIndex 對象
- 加速數(shù)據(jù)的檢索,可以在對象倉庫里面,為不同的屬性建立索引
- 指針:IDBCursor 對象
- 事務(wù):IDBTransaction 對象
- 數(shù)據(jù)記錄的讀寫和刪改,都要通過事務(wù)完成
- 事務(wù)對象提供error、abort和complete三個事件,用來監(jiān)聽操作結(jié)果
- 操作請求:IDBRequest 對象
- 主鍵集合:IDBKeyRange 對象
基礎(chǔ)操作
- 打開數(shù)據(jù)庫
- 建表
- 寫數(shù)據(jù)
- 查找數(shù)據(jù)
- 刪除數(shù)據(jù)
- 更改數(shù)據(jù)
- 刪除數(shù)據(jù)庫
下面通過一個例子來完整的演示上面的操作
1、首先定義一些變量,當調(diào)用的時候傳入給函數(shù),就可以新建到自己想要的數(shù)據(jù)庫和表
let defaultParams = {
indexdbName: '', // 數(shù)據(jù)庫的名字
indexdbVersion: 1, // 數(shù)據(jù)庫的版本
tableName: '', // 數(shù)據(jù)表的名字
data: '', // 影響的數(shù)據(jù)
unique: 'index' // keyPath(默認是主鍵為index)
}
2、打開數(shù)據(jù)庫
// 如果發(fā)現(xiàn)沒有數(shù)據(jù)庫存在則會創(chuàng)建,執(zhí)行
openIndexDB (params) {
// 如果瀏覽器本身不支持,則直接返回onupgradeneeded
// 如果數(shù)據(jù)庫已經(jīng)創(chuàng)建則會直接執(zhí)行onsuccess
if (!window.indexedDB) { return }
let _this = this
// 把用戶傳入的參數(shù)和默認參數(shù)做合并,作為后面建數(shù)據(jù)庫和建表的基礎(chǔ)
let obj = Object.assign({}, defaultParams, params)
let openDBRequest = window.indexedDB.open(obj.indexdbName, obj.indexdbVersion)
openDBRequest.onsuccess = function () {
// 獲取數(shù)據(jù)
_this.getData()
}
openDBRequest.onerror = function () {
console.log('openDBRequest onerror')
}
openDBRequest.onupgradeneeded = function (event) {
// request(即event.target)的result屬性是IDBDatabase類型
let db = event.target.result
// 創(chuàng)建一個對象存儲空間來存儲信息
// createObjectStore()方法返回的是IDBObjectStore類型的對象
let objectStore = db.createObjectStore(obj.tableName, { keyPath: obj.unique, autoIncrement: obj.unique === 'index' })
// 創(chuàng)建一個索引來快速查找
// 可能會有重復(fù)的,因此我們不能使用 unique 索引。
objectStore.createIndex('name', 'name', { unique: false })
// 存儲數(shù)據(jù)
// 在新創(chuàng)建的對象存儲空間中保存值
for (let i in _this[obj.data]) {
objectStore.add(_this[obj.data][i])
}
}
}
3、在表里面添加數(shù)據(jù)
addData (params) {
if (!window.indexedDB) { return }
let _this = this
let obj = Object.assign({}, defaultParams, params)
let openDBRequest = window.indexedDB.open(obj.indexdbName, obj.indexdbVersion)
openDBRequest.onsuccess = function (event) {
const db = event.target.result
// 找到表,然后設(shè)置readwrite
const transaction = db.transaction([obj.tableName], 'readwrite')
let objectStore = transaction.objectStore(obj.tableName)
for (let i in _this[obj.data]) {
// 添加數(shù)據(jù)
objectStore.add(_this[obj.data][i])
}
}
}
4、查找數(shù)據(jù)
getData (params) {
let _this = this
let obj = Object.assign({}, defaultParams, params)
let openDBRequest = window.indexedDB.open(obj.indexdbName, obj.indexdbVersion)
openDBRequest.onsuccess = function (event) {
const db = event.target.result
const transaction = db.transaction([obj.tableName], 'readwrite')
let objectStore = transaction.objectStore(obj.tableName)
// openCursor()方法返回一個IDBRequest類型的請求對象cursorRequest,
// 如果該請求對象cursorRequest成功,則cursorRequest的result屬性是IDBCursorWithValue類型的對象
let cursorRequest = objectStore.openCursor()
let list = []
cursorRequest.onsuccess = function (event) {
// cursorRequest的result屬性是IDBCursorWithValue類型的對象
// 利用游標找到所有的數(shù)據(jù)
let cursor = event.target.result
if (cursor) {
list.push(cursor.value)
cursor.continue()
} else {
// 把找到的數(shù)據(jù)塞到data里面
_this[obj.data] = list
}
}
}
}
5、刪除數(shù)據(jù)
// 刪除某一條數(shù)據(jù)
deleteData (params, id) {
if (!window.indexedDB) { return }
let obj = Object.assign({}, defaultParams, params)
let openDBRequest = window.indexedDB.open(obj.indexdbName, obj.indexdbVersion)
openDBRequest.onsuccess = function (event) {
const db = event.target.result
const transaction = db.transaction([obj.tableName], 'readwrite')
let objectStore = transaction.objectStore(obj.tableName)
// 根據(jù)id刪除數(shù)據(jù)
let request = objectStore.delete(id)
request.onsuccess = function (event) {
// console.log('刪除數(shù)據(jù)成功', event.target.result)
}
}
}
// 刪除該表的所有數(shù)據(jù)
deleteAll (params) {
if (!window.indexedDB) { return }
let obj = Object.assign({}, defaultParams, params)
let openDBRequest = window.indexedDB.open(obj.indexdbName, obj.indexdbVersion)
openDBRequest.onsuccess = function (event) {
const db = event.target.result
const transaction = db.transaction([obj.tableName], 'readwrite')
let objectStore = transaction.objectStore(obj.tableName)
// openCursor()方法返回一個IDBRequest類型的請求對象cursorRequest,
// 如果該請求對象cursorRequest成功,則cursorRequest的result屬性是IDBCursorWithValue類型的對象
let cursorRequest = objectStore.openCursor()
cursorRequest.onsuccess = function (event) {
let cursor = event.target.result
if (cursor) {
let request = objectStore.delete(cursor.key)
request.onsuccess = function (event) {}
cursor.continue()
} else {
console.log('刪除完成')
}
}
}
}
6、更改數(shù)據(jù)
updateData (params, newData) {
if (!window.indexedDB) { return }
let obj = Object.assign({}, defaultParams, params)
let openDBRequest = window.indexedDB.open(obj.indexdbName, obj.indexdbVersion)
openDBRequest.onsuccess = function (event) {
const db = event.target.result
const transaction = db.transaction([obj.tableName], 'readwrite')
let objectStore = transaction.objectStore(obj.tableName)
let request = objectStore.put(newData)
request.onsuccess = function (event) {
// console.log('更新數(shù)據(jù)成功')
}
request.onerror = function (event) {
// console.log('更新數(shù)據(jù)失敗')
}
}
}
7、刪除數(shù)據(jù)庫
deleteIndexDB (params) {
// 如果瀏覽器本身不支持,則直接返回onupgradeneeded
// 如果數(shù)據(jù)庫已經(jīng)創(chuàng)建則會直接執(zhí)行onsuccess
if (!window.indexedDB) { return }
let obj = Object.assign({}, defaultParams, params)
window.indexedDB.deleteDatabase(obj.indexdbName)
}
簡單的使用場景(vue實現(xiàn))
data () {
return {
list: [], // 用來渲染的列表數(shù)據(jù)
indexdbParams: { // 創(chuàng)建數(shù)據(jù)庫用的參數(shù)
indexdbName: 'indexedDBTest', // 數(shù)據(jù)庫的名字
indexdbVersion: 2, // 數(shù)據(jù)庫的版本
unique: 'index', // keyPath(默認是主鍵為index)
tableName: 'test', // 數(shù)據(jù)表的名字
data: 'list' // 影響的數(shù)據(jù)
}
}
},
created () {
// 先打開數(shù)據(jù)庫,并get數(shù)據(jù)
// success函數(shù)執(zhí)行了getData,所以會去獲取本地存儲的數(shù)據(jù)放到this.list,作為默認的展示
this.openIndexDB(this.indexdbParams)
// 同時發(fā)出請求獲取新的數(shù)據(jù)
this.getList()
}
methods: {
getList () {
fetch('/users.html')
.then(function(response) {
// 刪除上次存儲的數(shù)據(jù)
this.deleteAll(this.indexdbParams)
this.list = response.data
// 把新的數(shù)據(jù)寫進去
this.addData(this.indexdbParams)
})
}
}
現(xiàn)在在控制臺的Application下面的indexedDB里面就可以看到你存儲數(shù)據(jù)了,更新之后需要在表的那個地方點擊refresh database更新查看