IndexedDB瀏覽器數(shù)據(jù)庫(kù)介紹以及基本操作封裝

學(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)閉')
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容