IndexedDB
IndexedDB是瀏覽器儲存大量資料的方法,儲存形式為key-value。
- 儲存空間大,單一資料庫項目的容量/大小並沒有任何限制,但是各個 IndexedDB資料庫的容量就有限制。
- 交易性質資料庫模型,任何操作都需要有交易。
- 提供非同步API,不會回傳結果,需透過監聽來判斷成功或失敗。
- 同源政策:只能存取同網域底下的資料。
- 適合離線存儲使用,對PWA來說相當重要。
- 提供接口查詢,還能建立索引的方式來找資料。
對象種類
IDBFactory
IDBRequest
IDBOpenDBRequest
IDBVersionChangeEvent
IDBDatabase
IDBObjectStore
IDBIndex
IDBTransaction
IDBCursor
IDBCursorWithValue
IDBKeyRange
詳情請參考IndexedDB,嘗試把以上的關聯建立起來,整理出較常使用的方法。
以下記錄較常使用的方法
打開資料庫
const DB_NAME = 'TestDB';
const DB_VERSION = 1;
let DB;
function openDB(){
let request = window.indexedDB.open(DB_NAME, DB_VERSION)
request.onerror = e => {
console.log('打開資料庫出現錯誤', e)
}
request.onsuccess = e => {
DB = e.target.result
console.log('打開資料庫成功')
}
request.onupgradeneeded = e => {
console.log('資料庫版本更新')
}
}
window.indexDB 返回的是一個 IDBFactory 對象,提供了 open、deleteDatabase 等方法使用。
window.indexedDB.open 返回的是 IDBOpenDBRequest,它繼承了 IDBRequsest,只有 IDBOpenDBRequest 才有 onupgradeneeded 事件,用於檢查版本是否有更新,參數配置:
- 資料庫名稱
- 資料庫版本號
藉由事件監聽判斷結果,可以在 onsuccess 中的 e.target.result 取得 IDBDatabase 對象。
資料庫版本更新-創建倉儲 & 索引(非必要)
request.onupgradeneeded = e => {
let db = e.target.result
var temp = db.createObjectStore("showIndex", { keyPath:'id', autoIncrement: true })
temp.createIndex("NameColumns","name",{ unique:false })
}
把版本更新單獨拿出來看,更新的版本號需大於現存的才會呼叫,因此在此處會進行的操作是創建 or 刪除物件倉儲 (objectStore) 及創建 or 刪除索引 (index)。
物件倉儲類似於 SQL 的 table 概念,IDBDatabase.createObjectStore() 返回的是 IDBObjectStore,參數配置:
- 名稱:同資料庫不可重複
- 配置主鍵及自否自動增值( keyPath & autoIncrement ),組合出以下四種情況:
| keyPath | autoIncrement
| x | x | value 可以是任意類型,當新增新的資料時,必須指定鍵值
| o | x |{ keyPath:'ID' }
value 只能儲存 javascript 對象,物件內必須有與 keypath 同名的欄位
| x | o |{ autoIncrement:true }
value 可以是任意類型,鍵值會自動生成,和實際數據是分開的
| o | o |{ keyPath:'ID', autoIncrement:true }
value 只能儲存 javascript 對象,鍵值生成後會同時存在 value 中,若 value 有該鍵值欄位,則不會生成新的
而索引則是為了方便快速查詢,也可以不創建,後續會提到查詢的方法。參數配置:
- 顯示名稱:indexedDB 上顯示的名稱
- 欄位名稱:data 裡面要設置成索引的欄位名稱
- 設定 key 值是否可重複 & 是否給予陣列屬性獨立 index ( unique & multiEntry ):
{ unique: true, multiEntry:true }
,multiEntry 的範例:假設 data 內的 teams (陣列)為 index 索引值,若 multiEntry = false,則會顯示,['A','B','C'] {.....}。若 multiEntry = true,則會顯示三筆資料,'A' {...}、'B' {...}、'C' {...}
刪除資料庫
window.indexedDB.deleteDatabase(dbName);
創建交易
var transaction = db.transaction(["objectStore"], "readwrite")
var store = transaction.objectStore("objectStore")
transaction.oncomplete = () => {
console.log('完成')
}
transaction.onerror = (event) => {
console.log('錯誤')
}
transaction.onabort = (event) => {
console.log('中斷')
}
對資料進行操作前都需要先創建交易,參數配置:
- 此筆交易所涵蓋的 objectStoreName
- 該筆交易的模式,有 readonly(讀取)、readwrite(讀寫)、versionchange,預設為讀取交易
可通過回調來判斷交易結果,中斷:onabort()、錯誤:onerror()、完成:oncomplete()。
資料操作
資料操作前都須創建交易並透過 IDBTransaction.objectStore() 返回 IDBObjectStore,進行 CRUD 後返回的是 IDBRequest,監聽 onsuccess()、onerror() 取得結果,以下取出常用的方法來記錄,更多資訊可到 MDN 查看。
新增:add
let request = db.transaction(["todoList"],'readwrite')
.objectStore("todoList")
.add({ id:1, title:'吃飯', type:0 });
request.onsuccess = function(event) {
console.log('成功');
}
request.onerror = function(event) {
console.log('失敗');
}
以下監聽方式同上
更新:put
let request = db.transaction(["todoList"],'readwrite')
.objectStore("todoList")
.put({ id:1, title:'吃飯', type:1 });
更新的資料須傳入 Primary key。
刪除:delete、clear
let request = db.transaction(["todoList"],'readwrite')
.objectStore("todoList")
.delete(1);
刪除時傳入資料的 Primary key 即可。
let request = db.transaction(["todoList"],'readwrite')
.objectStore("todoList")
.clear();
清空 objectStore 裡面的所有數據。
讀取:get、getAll、openCursor、openKeyCursor( IDBObjectStore、 IDBIndex)
讀取資料比較特別,可以使用 IDBObjectStore 或是索引 IDBIndex 來快速搜尋資料,而索引的建立在一開始提到的版本升級時創建。
【get】
let request = db.transaction(["todoList"],'readonly')
.objectStore("todoList")
.get(1);
request.onsuccess = function(event) {
console.log('成功', event.target.result);
}
傳入 Primary key,找尋指定的資料,並透過 event.target.result 取得資料。
var transaction = db.transaction(['todoList']);
var objectStore = transaction.objectStore('todoList');
var index = objectStore.index('title');
var req = index.get('eat');
req.onsuccess = function(e) {
console.log('成功', event.target.result);
}
透過 IDBObjectStore.index() 取得 IDBIndex,針對 IDBIndex 快速查詢。
【getAll】
let request = db.transaction(["todoList"],'readonly')
.objectStore("todoList")
.getAll();
request.onsuccess = function(event) {
console.log('成功', event.target.result);
}
getAll 是取得該 objectStore 裡所有的資料。
let request = db.transaction(["todoList"],'readonly')
.objectStore("todoList")
.index('title')
.getAll();
request.onsuccess = function(event) {
console.log('成功', event.target.result);
}
IDBIndex 同樣也可操作 getAll()。
【openCursor】
cursor 是指標的概念,該方法跟 getAll 的差異在於是一筆一筆取資料,如果想要一次性讀取的話使用 getAll 較佳,若是想要一筆一筆資料就用 openCursor 。參數配置:
- 查詢範圍:IDBKeyRange 物件,能夠只聚焦在單一資料鍵上或者一段上下限區間,上下限區間可以是封閉(false,含界限)或開放(true,不含界限),可以針對 Primary key 或是利用索引搜尋的話就為 Index Key,預設為 false。
|IDBKeyRange.only(z)
| 鍵值 = z
|IDBKeyRange.upperBound(x, true)
| 鍵值 < x,(最大值,[boolean])
|IDBKeyRange.upperBound(x, false)
| 鍵值 ≤ x
|IDBKeyRange.lowerBound(y, true)
| 鍵值 > y,(最小值,[boolean])
|IDBKeyRange.lowerBound(y, true)
| 鍵值 ≥ y
|IDBKeyRange.bound(x, y, true, true)
| 鍵值 > x && < y
|IDBKeyRange.bound(x, y, true, false)
| 鍵值 > x && ≤ y
|IDBKeyRange.bound(x, y, false, false)
| 鍵值 ≥ x && ≤ y
|IDBKeyRange.bound(x, y, false, true)
| 鍵值 ≥ x && < y - 查詢方向:分為以下四種
| next | 由小到大
| prev | 由大到小
| nextunique | 若多筆相同鍵值時,由小到大的方向,取第一筆
| prevunique | 若多筆相同鍵值時,由大到小的方向,取第一筆
let trans = db.transaction(["todoList"],'readonly');
let store = trans.objectStore("todoList");
var request = store.openCursor();
request.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
console.log(
"primary key: " + cursor.key +
", value:" + cursor.value +
", 代辦清單名稱: " + cursor.value.name +
", 狀態: " + cursor.value.type
);
cursor.continue();
}
event.target.result 返回的是 IDBCursor 物件,這裡的 cursor.value 指的是 primary key 對應的整個資料,故可用 cursor.value.name 取得 name 欄位。索引的差別只在於 var request = store.index(索引名稱).openCursor(),結果是一樣的。cursor.continue()為執行下一筆的意思。
【openKeyCursor】
功能同 openCursor,差別在於 openKeyCursor 只能取得 Primary key 或是 Index key,參數配置也同上。
let trans = db.transaction(["todoList"],'readonly')
let store = trans.objectStore("todoList")
var request = store.openKeyCursor()
request.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
console.log(
"primary key: " + cursor.key +
", value:" + cursor.value
);
cursor.continue();
}
從圖片中可以看到 cursor.value 是 undefinded,只有 cursor.key 會有結果,所以使用 openKeyCursor 是無法取得到儲存對象的資料內容。索引的差別只在於 var request = store.index(索引名稱).openKeyCursor(),結果是一樣的。
總結
以上是使用 IndexedDB 相關的方法,紀錄一些常用的,更多資訊可以到 MDN 查看。
參考
https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
https://zhuanlan.zhihu.com/p/55830027
https://zhuanlan.zhihu.com/p/76393853
https://www.mifengjc.com/api/IDBKeyRange.html