IndexedDB 應用探索
IndexedDB 誕生背景
在開始之前,我們先簡單梳理一下瀏覽器儲存的幾種方式(詳見:point_right:瀏覽器儲存方式)
會話期 Cookie | 永續性 Cookie | sessionStorage | localStorage | indexedDB | WebSQL | |
---|---|---|---|---|---|---|
儲存大小 | 4kb | 4kb | 2.5~10 MB | 2.5~10 MB | >250MB | 已廢棄 |
失效時間 | 瀏覽器關閉自動清除 | 設定過期時間,到期後清除 | 瀏覽器關閉後清除 | 永久儲存(除非手動清除) | >手動更新或刪除 | 已廢棄 |
與服務端互動 | 有 | 有 | 無 | 無 | 無 | 已廢棄 |
訪問策略 | 符合同源策略可以訪問 | 符合同源策略可以訪問 | 符合同源策略可以訪問 | 即使同源也不可相互訪問 | 符合同源策略可以訪問 | 已廢棄 |
-
cookie
:儲存大小有限(4kb)、與服務端有互動,安全性較低、原生介面不友好,需要自己封裝、能設定過期時間 -
webStorage
:儲存空間較大,但有上限(2.5~10MB,各家瀏覽器不同)、與服務端無互動,安全性高、原生介面友好,資料操作比 cookie 方便
2.1localStorage
:持久化本地儲存,關閉瀏覽器重新開啟資料依然存在(除非手動刪除資料)
2.2sessionStorage
:瀏覽器視窗關閉後銷燬資料
cookie 和 webStorage 儲存資料格式僅支援 String,儲存時需要藉助JSON.stringify()
將 JSON 物件轉化為字串,讀取時需要藉助JSON.parse()
將字串轉化為 JSON 物件
一般來說,我們更推薦使用 webStroage,但其儲存大小有限、資料儲存僅支援 String 格式、不提供搜尋功能,不能建立自定義的索引。因此,需要一種新的解決方案,這就是IndexedDB
誕生的背景。
一、什麼是 IndexedDB ?
通俗地說,IndexedDB 就是瀏覽器提供的本地資料庫,它可以被網頁尾本建立和操作。IndexedDB 允許儲存大量資料,提供查詢介面,還能建立索引。這些都是 LocalStorage 所不具備的。就資料庫型別而言,IndexedDB 不屬於關係型資料庫(不支援 SQL 查詢語句),更接近 NoSQL 資料庫。
從 DB(Data Base) 可以看出它是一個數據庫。常用的資料庫有兩種型別:
-
關係型資料庫:資料儲存在表中,使用 sql 語句操作資料庫,如:
MySQL
、Oracle
、WebSQL
(已廢棄) -
非關係型資料庫:資料集作為 JSON 物件儲存,不需要寫sql 語句,如:
Redis
、MongoDB
、IndexedDB
IndexedDB 是非關係型資料庫,不需要寫 sql 語句進行資料庫操作,資料格式可使用 JSON 物件。
特性:
- 儲存空間大:沒有儲存上限,一般來說不小於 250M
-
儲存格式多樣:
物件倉庫 (object store)
- 非同步操作:效能更強。防止進行大量資料讀寫時,拖慢網頁(localStorage 的操作是同步的)
- 同源限制:每一個數據庫對應一個域名。只能訪問自身域名下的資料庫,不能跨域訪問
- 支援事務:在一系列操作步驟之中,如果有一步失敗,那麼整個事務就都會取消,資料庫回滾到事務發生之前的狀態,不存在只改寫一部分資料的情況
二、適用場景
- cookie:短期登陸,例如:token 會過期,需要設定過期時間,過期後重新換取 token
- sessionStorage:敏感賬號一次性登入
- localStorage:長期登入
- indexedDB:儲存大量結構化資料資料
對於簡單的資料,應該繼續使用 localStorage;對於大量結構化資料,indexedDB 會更適合。當然如果你需要設定過期時間的短期儲存,還是使用 cookie 儲存吧。
三、開始使用
基本步驟:
- 開啟(建立)資料庫,並開始一個事務
- 建立一個 object store
- 構建一個請求來執行一些資料庫操作,像增加或提取資料等
- 通過監聽正確型別的 DOM 事件以等待操作完成
- 在操作結果上進行一些操作(可以在 request 物件中找到)
(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>