學(xué)習(xí)阮一峰老師的IndexedDB入門(mén)教程
IndexedDB_API
IDBKeyRange
情況介紹
項(xiàng)目要求離線(xiàn)功能,也就是說(shuō)在不通網(wǎng)的情況下也可以進(jìn)行登錄以及支持?jǐn)?shù)據(jù)的增刪改查,這就要求說(shuō)的數(shù)據(jù)都要存儲(chǔ)在本地?cái)?shù)據(jù)庫(kù),可以從本地獲取數(shù)據(jù),在存儲(chǔ)的數(shù)據(jù)量較大的時(shí)候通過(guò)SessionStorage或者LocalStorage來(lái)進(jìn)行存儲(chǔ)是不合理的?,F(xiàn)有的瀏覽器數(shù)據(jù)儲(chǔ)存方案,都不適合儲(chǔ)存大量數(shù)據(jù):Cookie 的大小不超過(guò)4KB,且每次請(qǐng)求都會(huì)發(fā)送回服務(wù)器;LocalStorage 在 2.5MB 到 10MB 之間(各家瀏覽器不同),而且不提供搜索功能,不能建立自定義的索引。
而IndexedDB 是瀏覽器提供的本地?cái)?shù)據(jù)庫(kù),它可以被網(wǎng)頁(yè)腳本創(chuàng)建和操作。IndexedDB 允許儲(chǔ)存大量數(shù)據(jù),提供查找接口,還能建立索引。這些都是 SessionStorage或者LocalStorage 所不具備的。所以選擇IndexedDB。
IndexedDB 特點(diǎn)
1.鍵值對(duì)儲(chǔ)存
IndexedDB 內(nèi)部采用對(duì)象倉(cāng)庫(kù)(object store)存放數(shù)據(jù)。所有類(lèi)型的數(shù)據(jù)都可以直接存入,包括 JavaScript 對(duì)象。對(duì)象倉(cāng)庫(kù)中,數(shù)據(jù)以"鍵值對(duì)"的形式保存,每一個(gè)數(shù)據(jù)記錄都有對(duì)應(yīng)的主鍵,主鍵是獨(dú)一無(wú)二的,不能有重復(fù),否則會(huì)拋出一個(gè)錯(cuò)誤。
2.異步
IndexedDB 操作時(shí)不會(huì)鎖死瀏覽器,用戶(hù)依然可以進(jìn)行其他操作,這與 LocalStorage 形成對(duì)比,后者的操作是同步的。異步設(shè)計(jì)是為了防止大量數(shù)據(jù)的讀寫(xiě),拖慢網(wǎng)頁(yè)的表現(xiàn)。
3.支持事務(wù)
IndexedDB 支持事務(wù)(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個(gè)事務(wù)就都取消,數(shù)據(jù)庫(kù)回滾到事務(wù)發(fā)生之前的狀態(tài),不存在只改寫(xiě)一部分?jǐn)?shù)據(jù)的情況。
4.同源限制
IndexedDB 受到同源限制,每一個(gè)數(shù)據(jù)庫(kù)對(duì)應(yīng)創(chuàng)建它的域名。網(wǎng)頁(yè)只能訪(fǎng)問(wèn)自身域名下的數(shù)據(jù)庫(kù),而不能訪(fǎng)問(wèn)跨域的數(shù)據(jù)庫(kù)。
5.儲(chǔ)存空間大
IndexedDB 的儲(chǔ)存空間比 LocalStorage 大得多,一般來(lái)說(shuō)不少于 250MB,甚至沒(méi)有上限。
6.支持二進(jìn)制儲(chǔ)存
支持二進(jìn)制儲(chǔ)存。 IndexedDB 不僅可以?xún)?chǔ)存字符串,還可以?xún)?chǔ)存二進(jìn)制數(shù)據(jù)(ArrayBuffer 對(duì)象和 Blob 對(duì)象)
IndexedDB 基本操作封裝:
從數(shù)據(jù)庫(kù)新建、數(shù)據(jù)插入、數(shù)據(jù)查詢(xún)、數(shù)據(jù)刪除、數(shù)據(jù)庫(kù)關(guān)閉和刪除等方面進(jìn)行封裝
一、打開(kāi) / 創(chuàng)建數(shù)據(jù)庫(kù)
indexedDB.open()方法返回一個(gè) IDBRequest 對(duì)象。這個(gè)對(duì)象通過(guò)三種事件error、success、upgradeneeded,處理打開(kāi)數(shù)據(jù)庫(kù)的操作結(jié)果。新建數(shù)據(jù)庫(kù)與打開(kāi)數(shù)據(jù)庫(kù)是同一個(gè)操作。如果指定的數(shù)據(jù)庫(kù)不存在,就會(huì)新建。不同之處在于,后續(xù)的操作主要在upgradeneeded事件的監(jiān)聽(tīng)函數(shù)里面完成,因?yàn)檫@時(shí)版本從無(wú)到有,所以會(huì)觸發(fā)這個(gè)事件。
/**
* @param {object} dbName 數(shù)據(jù)庫(kù)的名字
* @param {string} storeName 倉(cāng)庫(kù)(表)名稱(chēng)
* @param {object} indexData 創(chuàng)建索引數(shù)據(jù)
* @param {string} version 數(shù)據(jù)庫(kù)的版本
* @param {string} keyPathValue 主鍵
* @return {object} 該函數(shù)會(huì)返回一個(gè)數(shù)據(jù)庫(kù)實(shí)例
*/
Vue.prototype.handleOpenDB = (dbName, storeName, indexData, keyPathValue, version = 1) => {
return new Promise((resolve, reject) => {
// 兼容瀏覽器
let indexedDB =
window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB
let db
const request = indexedDB.open(dbName, version)
request.onsuccess = function (event) {
db = event.target.result // 數(shù)據(jù)庫(kù)對(duì)象
console.log('數(shù)據(jù)庫(kù)打開(kāi)成功')
resolve(db)
}
request.onerror = function (event) {
console.log('數(shù)據(jù)庫(kù)打開(kāi)報(bào)錯(cuò)')
}
request.onupgradeneeded = function (event) { // 數(shù)據(jù)庫(kù)創(chuàng)建或升級(jí)的時(shí)候會(huì)觸發(fā)
console.log('onupgradeneeded')
db = event.target.result // 數(shù)據(jù)庫(kù)對(duì)象
storeData.forEach((item) => {
let objectStore
if (!db.objectStoreNames.contains(item.storeName)) { // 表格是否存在,如果不存在新建
// eslint-disable-next-line standard/object-curly-even-spacing
objectStore = db.createObjectStore(item.storeName, { keyPath: item.keyValue, autoIncrement: true}) // 創(chuàng)建表,keyPathValue為主鍵
// 創(chuàng)建索引 可以讓你搜索任意字段 IDBObject.createIndex()的三個(gè)參數(shù)分別為索引名稱(chēng)、索引所在的屬性、配置對(duì)象(說(shuō)明該屬性是否包含重復(fù)的值)。
item.storeIndexData.forEach((store) => {
if (store) objectStore.createIndex(store.filedName, store.filedName, { unique: store.unique })
})
} else { // 表格存在,需要新增索引
objectStore = event.target.transaction.objectStore(item.storeName)
let indexNames = objectStore.indexNames
item.storeIndexData.forEach((store) => {
if (!indexNames.contains(store.filedName)) {
objectStore.createIndex(store.filedName, store.filedName, { unique: store.unique })
}
})
}
})
}
})
}
二、數(shù)據(jù)新增和更新(支持批量)
寫(xiě)入數(shù)據(jù)需要新建一個(gè)事務(wù)。新建時(shí)必須指定表格名稱(chēng)和操作模式("只讀"或"讀寫(xiě)")。新建事務(wù)以后,通過(guò)IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 對(duì)象,再通過(guò)表格對(duì)象的add()/put()方法,向表格寫(xiě)入記錄。
/**
* 新增數(shù)據(jù)(添加數(shù)據(jù),重復(fù)添加會(huì)報(bào)錯(cuò))
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)名稱(chēng)
* @param {string} data 存儲(chǔ)數(shù)據(jù)
* @param {string} readWriteType 數(shù)據(jù)庫(kù)讀寫(xiě)類(lèi)型
*/
Vue.prototype.handleAddData = (db, storeName, data, readWriteType = 'readwrite') => {
let store = db.transaction([storeName], readWriteType) // 事務(wù)對(duì)象 指定表格名稱(chēng)和操作模式("只讀"或"讀寫(xiě)")
.objectStore(storeName) // 倉(cāng)庫(kù)對(duì)象
data.forEach((item, index) => {
let request = store.add(data[index])
request.onerror = function () {
console.error('添加數(shù)據(jù)庫(kù)中已有該數(shù)據(jù)')
}
request.onsuccess = function () {
console.log('添加數(shù)據(jù)已存入數(shù)據(jù)庫(kù)')
}
})
}
/**
* 更新數(shù)據(jù)(重復(fù)數(shù)據(jù)更新,新的數(shù)據(jù)添加)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)名稱(chēng)
* @param {object} data 數(shù)據(jù)
* @param {string} readWriteType 數(shù)據(jù)庫(kù)讀寫(xiě)類(lèi)型
*/
Vue.prototype.handleUpdateData = (db, storeName, data, readWriteType = 'readwrite') => {
let store = db
.transaction([storeName], readWriteType) // 事務(wù)對(duì)象
.objectStore(storeName) // 倉(cāng)庫(kù)對(duì)象
data.forEach((item, index) => {
let request = store.put(data[index])
request.onerror = function () {
console.error('數(shù)據(jù)更新失敗')
}
request.onsuccess = function () {
console.log('數(shù)據(jù)更新成功')
}
})
}
三、數(shù)據(jù)讀取
-
通過(guò)主鍵讀取數(shù)據(jù)
* 通過(guò)主鍵讀取數(shù)據(jù)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)(表)名稱(chēng)
* @param {string} key 主鍵值
*/
Vue.prototype.getDataByKey = (db, storeName, key) => {
return new Promise((resolve, reject) => {
var transaction = db.transaction([storeName]) // 事務(wù)
var objectStore = transaction.objectStore(storeName) // 倉(cāng)庫(kù)對(duì)象
var request = objectStore.get(key) // 參數(shù)key是主鍵
request.onerror = function (event) {
console.log('主鍵查詢(xún)失敗')
}
request.onsuccess = function (event) {
console.log('主鍵查詢(xún)結(jié)果: ', request.result)
resolve(request.result)
}
})
}
-
通過(guò)游標(biāo)讀取倉(cāng)庫(kù)(表)內(nèi)所有數(shù)據(jù)
/**
* 通過(guò)游標(biāo)讀取倉(cāng)庫(kù)內(nèi)所有數(shù)據(jù)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)(表)名稱(chēng)
* @param {string} readWriteType 數(shù)據(jù)庫(kù)讀寫(xiě)類(lèi)型
*/
Vue.prototype.cursorGetData = (db, storeName, readWriteType = 'readwrite') => {
return new Promise((resolve, reject) => {
let allData = []
let request = db
.transaction(storeName, readWriteType) // 事務(wù)
.objectStore(storeName) // 倉(cāng)庫(kù)對(duì)象
.openCursor() // 指針對(duì)象
request.onsuccess = function (e) {
let cursor = e.target.result
if (cursor) {
allData.push(cursor.value)
cursor.continue() // 遍歷表內(nèi)的所有內(nèi)容
} else {
console.log('游標(biāo)查詢(xún)結(jié)果:', allData)
resolve(allData)
}
}
})
}
-
通過(guò)索引讀取數(shù)據(jù)
索引的意義在于,可以讓你搜索任意字段,也就是說(shuō)從任意字段拿到數(shù)據(jù)記錄。如果不建立索引,默認(rèn)只能搜索主鍵(即從主鍵取值)。
/**
* 通過(guò)索引讀取數(shù)據(jù)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)(表)名稱(chēng)
* @param {string} indexName 索引名稱(chēng)
* @param {string} indexValue 索引值
* @param {string} readWriteType 數(shù)據(jù)庫(kù)讀寫(xiě)類(lèi)型
*/
Vue.prototype.getDataByIndex = (db, storeName, indexName, indexValue, readWriteType = 'readwrite') => {
return new Promise((resolve, reject) => {
let request = db
.transaction(storeName, readWriteType)
.objectStore(storeName)
.index(indexName)
.get(indexValue)
request.onerror = function () {
console.log('事務(wù)失敗')
}
request.onsuccess = function (e) {
let result = e.target.result
console.log('索引查詢(xún)結(jié)果:', result)
resolve(result)
}
})
}
-
過(guò)索引和游標(biāo)查詢(xún)指定范圍數(shù)據(jù)
/**
* 通過(guò)索引和游標(biāo)查詢(xún)指定范圍數(shù)據(jù)(日期范圍示例,查詢(xún)indexName值t1到t2范圍內(nèi)的)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)名稱(chēng)
* @param {string} indexName 索引名稱(chēng)
* @param {number} t1 日期1
* @param {number} t2 日期2
* @param {string} readWriteType 數(shù)據(jù)庫(kù)讀寫(xiě)類(lèi)型
*/
Vue.prototype.getDataByTimeRange = (db, storeName, indexName, t1, t2, readWriteType = 'readwrite') => {
return new Promise((resolve, reject) => {
let result = []
let request = db
.transaction(storeName, readWriteType) // 事務(wù)
.objectStore(storeName) // 倉(cāng)庫(kù)對(duì)象
.index(indexName)
.openCursor(IDBKeyRange.bound(t1, t2))
request.onsuccess = function (e) {
let cursor = e.target.result
if (cursor) {
// 必須要檢查
result.push(cursor.value)
cursor.continue() // 遍歷了存儲(chǔ)對(duì)象中的所有內(nèi)容
} else {
console.log('范圍查詢(xún)結(jié)果:', result)
resolve(result)
}
}
request.onerror = function (event) {
console.log('事務(wù)失敗')
}
})
}
四、數(shù)據(jù)刪除
-
刪除主鍵對(duì)應(yīng)的數(shù)據(jù)
/**
* 刪除主鍵對(duì)應(yīng)的數(shù)據(jù)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)名稱(chēng)
* @param {object} key 主鍵值
* @param {string} readWriteType 數(shù)據(jù)庫(kù)讀寫(xiě)類(lèi)型
*/
Vue.prototype.handleDeleteByKey = (db, storeName, key, readWriteType = 'readwrite') => {
let request = db
.transaction([storeName], readWriteType)
.objectStore(storeName)
.delete(key) // 刪除主鍵對(duì)應(yīng)的記錄
request.onsuccess = function () {
console.log('數(shù)據(jù)刪除成功')
}
request.onerror = function () {
console.log('數(shù)據(jù)刪除失敗')
}
}
-
刪除整張表的數(shù)據(jù)
/**
* 刪除整張表的數(shù)據(jù)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
* @param {string} storeName 倉(cāng)庫(kù)名稱(chēng)
* @param {string} readWriteType 數(shù)據(jù)庫(kù)讀寫(xiě)類(lèi)型
*/
Vue.prototype.handleDeleteAll = (db, storeName, readWriteType = 'readwrite') => {
let request = db
.transaction([storeName], readWriteType)
.objectStore(storeName)
.clear() // 刪除整張表記錄
request.onsuccess = function () {
console.log('刪除整張表的數(shù)據(jù)成功')
}
request.onerror = function () {
console.log('刪除整張表的數(shù)據(jù)失敗')
}
}
五、數(shù)據(jù)庫(kù)的刪除和關(guān)閉
-
刪除數(shù)據(jù)庫(kù)
/**
* 刪除數(shù)據(jù)庫(kù)
* @param {object} dbName 數(shù)據(jù)庫(kù)名稱(chēng)
*/
Vue.prototype.handleDeleteDB = (dbName) => {
let deleteRequest = window.indexedDB.deleteDatabase(dbName)
deleteRequest.onerror = function (event) {
console.log('刪除失敗')
}
deleteRequest.onsuccess = function (event) {
console.log('刪除成功')
}
}
-
關(guān)閉數(shù)據(jù)庫(kù)
/**
* 關(guān)閉數(shù)據(jù)庫(kù)
* @param {object} db 數(shù)據(jù)庫(kù)實(shí)例
*/
Vue.prototype.handleCloseDB = (db) => {
db.close()
console.log('數(shù)據(jù)庫(kù)已關(guān)閉')
}