IndexedDB 應用探索

提及 IndexedDB,你們應該會有一些疑問,好比:什麼是 IndexedDB?適合什麼業務場景?哪些公司哪些業務已經開始使用 indexedDB了?帶着這些問題,閱讀本文,相信可以給你答案。html

IndexedDB 誕生背景

在開始以前,咱們先簡單梳理一下瀏覽器存儲的幾種方式(詳見👉瀏覽器存儲方式vue

會話期 Cookie 持久性 Cookie sessionStorage localStorage indexedDB WebSQL
存儲大小 4kb 4kb 2.5~10 MB 2.5~10 MB >250MB 已廢棄
失效時間 瀏覽器關閉自動清除 設置過時時間,到期後清除 瀏覽器關閉後清除 永久保存(除非手動清除) 手動更新或刪除 已廢棄
與服務端交互 已廢棄
訪問策略 符合同源策略能夠訪問 符合同源策略能夠訪問 符合同源策略能夠訪問 即便同源也不可相互訪問 符合同源策略能夠訪問 已廢棄

WebSQL 是一種瀏覽器存儲方案,屬於傳統的關係型數據庫,須要寫 sql 語句查詢。WebSQL 出現過一段時間,雖然已被部分瀏覽器支持,但又被廢棄,由 IndexedDB 取代。
廢棄緣由:
This document was on the W3C Recommendation track but specification work has stopped. The specification reached an impasse: all interested implementors have used the same SQL backend (Sqlite), but we need multiple independent implementations to proceed along a standardisation path.
大意:
該文件是W3C推薦標準,但規範的制定工做已經中止。該規範陷入僵局:全部感興趣的實現者都使用了相同的SQL後端(SQLite的),但咱們須要多個獨立的實現沿着規範化的路徑進行。web

cookie 和 webStorage 存儲數據格式僅支持 String,存儲時須要藉助 JSON.stringify() 將 JSON 對象轉化爲字符串,讀取時須要藉助 JSON.parse() 將字符串轉化爲 JSON 對象。sql

通常來講,咱們更推薦使用 webStroage,但其存儲大小有限、數據存儲僅支持 String 格式、不提供搜索功能,不能創建自定義的索引。所以,須要一種新的解決方案,這就是 IndexedDB 誕生的背景。vuex

1、什麼是 IndexedDB ?

通俗地說,IndexedDB 就是瀏覽器提供的本地數據庫,它能夠被網頁腳本建立和操做。IndexedDB 容許儲存大量數據,提供查找接口,還能創建索引。這些都是 LocalStorage 所不具有的。就數據庫類型而言,IndexedDB 不屬於關係型數據庫(不支持 SQL 查詢語句),更接近 NoSQL 數據庫。

從 DB(Data Base) 能夠看出它是一個數據庫。經常使用的數據庫有兩種類型:數據庫

  • 關係型數據庫:數據存儲在表中,使用 sql 語句操做數據庫,如:MySQLOracleWebSQL(已廢棄)
  • 非關係型數據庫:數據集做爲 JSON 對象存儲,不須要寫sql 語句,如:RedisMongoDBIndexedDB

IndexedDB 是非關係型數據庫,不須要寫 sql 語句進行數據庫操做,數據格式可以使用 JSON 對象。segmentfault

IndexedDB 有不少優勢:後端

  • 存儲空間大:沒有存儲上限,通常來講不小於 250M
  • 存儲格式多樣:跨域

    • 支持字符串存儲
    • 支持二進制存儲(ArrayBuffer 對象和 Blob 對象)
    • 支持 JSON 鍵值對存儲,一個對象至關於關係型數據庫中的數據表,咱們稱其爲 對象倉庫 (object store)
  • 異步操做:性能更強。防止進行大量數據讀寫時,拖慢網頁(localStorage 的操做是同步的)
  • 同源限制:每個數據庫對應一個域名。只能訪問自身域名下的數據庫,不能跨域訪問
  • 支持事務:在一系列操做步驟之中,若是有一步失敗,那麼整個事務就都會取消,數據庫回滾到事務發生以前的狀態,不存在只改寫一部分數據的狀況

2、適用場景

  • cookie:短時間登錄,例如:token 會過時,須要設置過時時間,過時後從新換取 token
  • sessionStorage:敏感帳號一次性登陸
  • localStorage:長期登陸
  • indexedDB:存儲大量結構化數據數據

對於簡單的數據,應該繼續使用 localStorage;對於大量結構化數據,indexedDB 會更適合。固然若是你須要設置過時時間的短時間存儲,仍是使用 cookie 存儲吧。瀏覽器

3、開始使用

基本步驟:
  • 打開(建立)數據庫,並開始一個事務
  • 建立一個 object store
  • 構建一個請求來執行一些數據庫操做,像增長或提取數據等
  • 經過監聽正確類型的 DOM 事件以等待操做完成
  • 在操做結果上進行一些操做(能夠在 request 對象中找到)

下面給出了三種使用方案,你能夠用最簡單的原生API 進行基本操做,也能夠本身封裝一個庫,也能夠用第三方庫。

(1)基礎調用

(2)本身封裝

<script>
  var myDB = {
    name: 'school', // 數據庫名
    version: 1, // 數據庫版本號
    db: null,
    ojstore: {
      name: 'teachers', // 存儲空間表的名字
      keypath: 'id', // 主鍵
      indexKey: 'age' // 年齡索引
    }
  }

  var INDEXDB = {
    indexedDB: window.indexedDB || window.webkitindexedDB,
    IDBKeyRange: window.IDBKeyRange || window.webkitIDBKeyRange, // 鍵範圍
    // 打開或建立數據庫,創建對象存儲空間 ObjectStore
    openDB: function (dbname, dbversion) {
      var that = this
      var version = dbversion || 1
      var request = that.indexedDB.open(dbname, version)

      request.onerror = function (e) {
        console.log(e.currentTarget.error.message)
      }

      request.onsuccess = function (e) {
        myDB.db = e.target.result
        console.log('成功創建並打開數據庫:' + myDB.name + 'version' + dbversion)
      }

      request.onupgradeneeded = function (e) {
        var db = e.target.result
        var transaction = e.target.transaction
        var store

        if (!db.objectStoreNames.contains(myDB.ojstore.name)) {
          //沒有該對象空間時建立該對象空間
          store = db.createObjectStore(myDB.ojstore.name, {
            keyPath: myDB.ojstore.keypath
          })
          console.log('成功創建對象存儲空間:' + myDB.ojstore.name)
          that.storeIndex(store, myDB.ojstore.indexKey)
        }
      }
    },
    // 刪除數據庫
    deletedb: function (dbname) {
      var that = this
      that.indexedDB.deleteDatabase(dbname)
      console.log(dbname + '數據庫已刪除')
    },
    // 關閉數據庫
    closeDB: function (db) {
      db.close()
      console.log('數據庫已關閉')
    },
    // 添加數據,重複添加會報錯
    addData: function (db, storename, data) {
      var store = db.transaction(storename, 'readwrite').objectStore(storename)
      var request

      for(var i = 0; i < data.length; i++) {
        request = store.add(data[i])

        request.onerror = function() {
          console.error('add添加數據庫中已有該數據')
        }

        request.onsuccess = function() {
          console.log('add添加數據已存入數據庫')
        }
      }
    },
    // 經過遊標查詢記錄
    cursorGetData: function (db, storename, keyRange) {
      var keyRange = keyRange || ''
      var store = db.transaction(storename, 'readwrite').objectStore(storename)
      var request = store.openCursor(keyRange)

      request.onsuccess = function (e) {
        var cursor = e.target.result

        if (cursor) { // 必需要檢查
          console.log(cursor)
          cursor.continue() // 遍歷了存儲對象中的全部內容
        } else{
          console.log('遊標查詢結束')
        }
      }
    },
    // 經過索引遊標查詢記錄
    cursorGetDataByIndex: function (db, storename, keyRange) {
      var keyRange = keyRange || ''
      var store = db.transaction(storename, 'readwrite').objectStore(storename)
      var request = store.index('age').openCursor(keyRange)

      request.onsuccess = function (e) {
        console.log('遊標開始查詢')
        var cursor = e.target.result

        if (cursor) {//必需要檢查
          console.log(cursor)
          cursor.continue()//遍歷了存儲對象中的全部內容
        } else {
          console.log('遊標查詢結束')
        }
      }
    },
    // 經過遊標更新記錄
    cursorUpdateData: function (db, storename) {
      var keyRange = keyRange || ''
      var store = db.transaction(storename,'readwrite').objectStore(storename)
      var request = store.openCursor()

      request.onsuccess = function (e) {
        console.log('遊標開始查詢')
        var cursor = e.target.result
        var value, updateRequest

        if (cursor) { // 必需要檢查
          console.log(cursor)
          if (cursor.key === 1002) {
            console.log('遊標開始更新')
            value = cursor.value
            value.age = 38
            updateRequest = cursor.update(value)

            updateRequest.onerror = function () {
              console.log('遊標更新失敗')
            }

            updateRequest.onsuccess = function () {
              console.log('遊標更新成功')
            }
          } else {
            cursor.continue()
          }
        }
      }
    },
    // 經過遊標刪除記錄
    cursorDeleteData: function (db, storename) {
      var keyRange = keyRange || ''
      var store = db.transaction(storename, 'readwrite').objectStore(storename)
      var request = store.openCursor()

      request.onsuccess = function (e) {
        var cursor = e.target.result
        var value, deleteRequest

        if (cursor) {
          if (cursor.key === 1003) {
            deleteRequest = cursor.delete() // 請求刪除當前項
            deleteRequest.onerror = function () {
              console.log('遊標刪除該記錄失敗')
            }

            deleteRequest.onsuccess = function () {
              console.log('遊標刪除該記錄成功')
            }
          } else {
            cursor.continue()
          }
        }
      }
    },
    // 建立索引
    storeIndex: function (store, indexKey) {
      var index = store.createIndex(indexKey, indexKey, {
        unique:false
      })
      console.log('建立索引' + indexKey + '成功')
    }
  }

  var teachers = [{ 
    id:1001, 
    name:'Byron', 
    age:21 
  }, {
    id:1002, 
    name:'Frank', 
    age:22
  }, {
    id:1003, 
    name:'Aaron', 
    age:23 
  }, {
    id:1004, 
    name:'Aaron', 
    age:24 
  }, {
    id:1005, 
    name:'Byron', 
    age:24 
  }, {
    id:1006, 
    name:'Frank', 
    age:30 
  }, {
    id:1007, 
    name:'Aaron', 
    age:26 
  }, {
    id:1008, 
    name:'Aaron', 
    age:27 
  }]

  INDEXDB.openDB(myDB.name, myDB.version)

  setTimeout(function() {
    // 添加數據
    INDEXDB.addData(myDB.db, myDB.ojstore.name, teachers)

    // 遊標更新數據id1002更新其age爲38
    INDEXDB.cursorUpdateData(myDB.db, myDB.ojstore.name)

    // 遊標刪除id爲1003的數據
    // INDEXDB.cursorDeleteData(myDB.db, myDB.ojstore.name)

    // 關閉數據庫
    // INDEXDB.closeDB(myDB.db)

    // 刪除數據庫
    // INDEXDB.deletedb(myDB.db)

    /*
     *遊標鍵範圍方法調用
     */
    var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange    

    // 查找1004對象
    // var onlyKeyRange = IDBKeyRange.only(1004)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, onlyKeyRange)
    
    // 查找從1004對象開始
    // var lowerBoundKeyRange = IDBKeyRange.lowerBound(1004)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, lowerBoundKeyRange)
    
    // 查找從1004對象開始不包括1004
    // var lowerBoundKeyRangeTrue = IDBKeyRange.lowerBound(1004, true)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, lowerBoundKeyRangeTrue)
    
    // 查找到1004對象結束
    // var upperBoundKeyRange = IDBKeyRange.upperBound(1004)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, upperBoundKeyRange)
    
    // 查找到1004對象結束不包括1004
    // var upperBoundKeyRangeTrue = IDBKeyRange.upperBound(1004, true)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, upperBoundKeyRangeTrue)
    
    // 查找到1002到1004對象
    // var boundKeyRange = IDBKeyRange.bound(1002, 1004)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, boundKeyRange)
    
    // 查找到1002到1004對象不包括1002
    // var boundKeyRangeLowerTrue = IDBKeyRange.bound(1002, 1004, true)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, boundKeyRangeLowerTrue)
    
    // 查找到1002到1004對象包括1002不包括1004
    // var boundKeyRangeUpperTrue = IDBKeyRange.bound(1002, 1004, false, true)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, boundKeyRangeUpperTrue)
    
    // 查找到1002到1004對象不包括1002不包括1004
    // var boundKeyRangeLTUT = IDBKeyRange.bound(1002, 1004, true, true)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, boundKeyRangeLTUT)

    /*
     *存儲鍵遊標查詢與索引鍵遊標查詢對比
     */
    
    // 存儲鍵遊標查詢
    // var onlyKeyRange = IDBKeyRange.only(1004)
    // INDEXDB.cursorGetData(myDB.db, myDB.ojstore.name, onlyKeyRange)
    
    // 索引鍵遊標查詢
    // var onlyKeyRange = IDBKeyRange.only(30)
    // INDEXDB.cursorGetDataByIndex(myDB.db, myDB.ojstore.name, onlyKeyRange)
  }, 1000)
</script>

(3)第三方庫:Dexie.js

Dexie.js 是 indexedDB 封裝 SDK,API 簡潔強大、錯誤處理簡單而強壯。

官方文檔: https://dexie.org/
<!doctype html>
<html>
  <head>
    <!-- Include Dexie -->
    <script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>

    <script>
        // Define your database
        var db = new Dexie("student_database");
        db.version(1).stores({
            students: 'name'
        });

        //
        // Put some data into it
        //
        var data = {
          name: "Byron",
          shoeSize: 24
        }

        db.students.put(data).then (function (){
            //
            // Then when data is stored, read from it
            //
            return db.students.get('Nicolas');
        }).then(function (student) {
            //
            // Display the result
            //
            alert ("Nicolas has shoe size " + student.shoeSize);
        }).catch(function(error) {
           //
           // Finally don't forget to catch any error
           // that could have happened anywhere in the
           // code blocks above.
           //
           alert ("Ooops: " + error);
        });
    </script>
  </head>
</html>

4、哪些公司在使用?

IndexedDB 目前已在美團的部分業務中使用,其餘公司尚不清楚,但我相信數據量較大且須要緩存的業務必定能夠用到 IndexedDB。

一些想法:

IndexedDB 是否能夠結合 Vuex 使用?(localStorage 或許也能知足需求)

Vuex 將咱們須要共享的數據放入一個公共的變量中,以便在路由跳轉時重複使用,由於路由跳轉是無刷新頁面的,因此數據不會丟失。可是當咱們刷新或跳轉頁面時,數據就會丟失。這時候就可使用 IndexedDB 或 localStorage 將數據保存在其中既可以避免數據丟失,又能避免路由跳轉時沒必要要的存儲操做。

created () {
    // 頁面加載時,讀取 IndexedDB
    var data = indexedDB.getData('myDB', 'myStore', 'myData')
    // var data = window.localStorage.getItem('myData')
    var storeData = Object.assign(this.$store.state, data)
    this.$store.replaceState(storeData)

    // 頁面刷新時將 vuex 裏的信息保存到 IndexedDB
    window.addEventListener('beforeunload', () => {
        indexedDB.updateData('myDB', this.$store.state)
        // window.localStorage.setItem(this.$store.state)
    })
}
參考文章:

http://www.ruanyifeng.com/blo...

相關文章
相關標籤/搜索