【瀏覽器資料存取】IndexedDB


Posted by LilyLiu on 2021-08-04

IndexedDB

IndexedDB是瀏覽器儲存大量資料的方法,儲存形式為key-value。

  1. 儲存空間大,單一資料庫項目的容量/大小並沒有任何限制,但是各個 IndexedDB資料庫的容量就有限制。
  2. 交易性質資料庫模型,任何操作都需要有交易。
  3. 提供非同步API,不會回傳結果,需透過監聽來判斷成功或失敗。
  4. 同源政策:只能存取同網域底下的資料。
  5. 適合離線存儲使用,對PWA來說相當重要。
  6. 提供接口查詢,還能建立索引的方式來找資料。

對象種類

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 事件,用於檢查版本是否有更新,參數配置:

  1. 資料庫名稱
  2. 資料庫版本號

藉由事件監聽判斷結果,可以在 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,參數配置:

  1. 名稱:同資料庫不可重複
  2. 配置主鍵及自否自動增值( 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 有該鍵值欄位,則不會生成新的

而索引則是為了方便快速查詢,也可以不創建,後續會提到查詢的方法。參數配置:

  1. 顯示名稱:indexedDB 上顯示的名稱
  2. 欄位名稱:data 裡面要設置成索引的欄位名稱
  3. 設定 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('中斷')
}

對資料進行操作前都需要先創建交易,參數配置:

  1. 此筆交易所涵蓋的 objectStoreName
  2. 該筆交易的模式,有 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 。參數配置:

  1. 查詢範圍: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
  2. 查詢方向:分為以下四種
    | 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


#indexedDB







Related Posts

JS review

JS review

Ceres 函式庫簡介

Ceres 函式庫簡介

this 與 call() / apply() / bind()

this 與 call() / apply() / bind()


Comments