IndexedDB簡介與入門
背景
在本地離線存儲的場景中,IndexedDB作為一個nosql的本地DB存儲一直發(fā)揮著重要的作用。在需要存儲大量數(shù)據(jù)時,IndexedDB能夠有效地滿足你的需求。下面,我將簡單介紹下IndexedDB的簡單使用方法和在我閱讀W3C規(guī)范文檔時看到的一些需要注意的細節(jié)。實例大部分參照MDN中的介紹實例,同時對整體邏輯和實例做了一些改動。
使用方法
打開數(shù)據(jù)庫
我們使用window.indexedDB.open(DBName)來打開數(shù)據(jù)庫。具體實例如下:
- const request = window.indexedDB.open(DBName);
- request.onsuccess = function(event) {
- //request === event.target;
- }
- request.onerror = function(event) {}
當數(shù)據(jù)庫連接時,會返回一個IDBOpenDBRequest對象。在連接建立成功時,會觸發(fā)onsuccess事件,其中函數(shù)參數(shù)event的target屬性就是request對象。
創(chuàng)建和更新數(shù)據(jù)庫版本號
window.indexedDB.open的第二個參數(shù)即為版本號。在不指定的情況下,默認版本號為1。具體實例如下:
- const request = window.indexedDB.open(DBName, 2);
在需要更新數(shù)據(jù)庫的schema(模式)時,需要更新版本號。此時我們指定一個高于之前版本的版本號,就會觸發(fā)onupgradeneeded事件。類似的,在***次創(chuàng)建時,也會觸發(fā)此事件。
我們需要注意的是,版本號是一個unsigned long long數(shù)字,這意味著它可以是一個非常大的整數(shù)。但是,它不能是一個小數(shù),否則它將會被轉(zhuǎn)為最近的整數(shù),同時有可能導(dǎo)致onUpgradeneeded事件不觸發(fā)(bug)。
構(gòu)建數(shù)據(jù)庫
我們使用createObjectStore來創(chuàng)建一個存儲空間。同時,使用createIndex來創(chuàng)建索引。具體實例如下:
- const objectStore = db.createObjectStore('customers');
- objectStore.createIndex('name', 'name', {unique:false});
我們從***個函數(shù)createObjectStore開始說起。該函數(shù)接受兩個參數(shù),***個參數(shù)為存儲空間的名稱,即我們上面的customers。同時,它還有第二個可選參數(shù),keyPath指定存儲的key值為存儲對象的某個屬性,autoIncrement指定了key值是否自增(當key值為默認的從1開始到2^53的整數(shù)時)。具體實例如下:
- const objectStore = db.createObjectStore('customers',{keyPath:'id', autoIncrement:true});
第二個函數(shù)為createIndex,它的***個參數(shù)為索引的名稱,第二個參數(shù)是指定根據(jù)存儲數(shù)據(jù)的哪一個屬性來構(gòu)建索引,第三個屬性unique為是否允許指定的索引值是否可以相等。具體示例如下:
- objectStore.createIndex('by_name', 'name', {unique:false});
- objectStore.createIndex('by_email', 'email', {unique:true});
當存儲空間初始化完成后,我們需要把數(shù)據(jù)放入存儲空間中,我們可以直接調(diào)用add方法將數(shù)據(jù)放入存儲空間內(nèi),具體實例如下:
- //方法1,規(guī)范文檔推薦使用, key值如果指定自增,可以不填
- objectStore.put(data, key);
- //方法2,key值同上
- objectStore.add(data, key);
事務(wù)
在IndexedDB中,我們也能夠使用事務(wù)來進行數(shù)據(jù)庫的操作。事務(wù)有三個模式:
- readOnly,只讀
- readwrite,讀寫
- versionchange,數(shù)據(jù)庫版本變化
我們創(chuàng)建一個事務(wù)時,需要從上面選擇一種模式,如果不指定的話,則默認為只讀模式。具體實例如下:
- const transition = db.transition(['customers'], 'readwrite');
事務(wù)函數(shù)transition的***個參數(shù)為需要關(guān)聯(lián)的存儲空間,第二個可選參數(shù)為事務(wù)模式。與上面類似,事務(wù)成功時也會觸發(fā)onsuccess函數(shù),失敗時觸發(fā)onerror函數(shù)。
事務(wù)的操作都是原子性的。
刪除
使用delete函數(shù)即可刪除數(shù)據(jù)。具體使用方法如下:
- const request = db.transaction(['customers'], 'readwrite').objectStore('customers').delete(author);
獲取數(shù)據(jù)
get方法
最簡單的方法就是使用自帶的get函數(shù)來獲取存儲空間的值,具體實例如下:
- db.transition(['customer']).objectStore('customer').get(keyName).onuccess = function(){};
游標方法
我們使用openCursor來創(chuàng)建游標。
- const objectStore = db.transaction('customers').objectStore('customers');
- objectStore.openCursor().onsuccess = function(event) {
- const cursor = event.target.result;
- if (cursor) {
- alert(cursor.value.name);
- cursor.continue();
- }
- else {
- alert('No more entries!');
- }
- };
使用游標時有一個需要注意的地方,當游標便利整個存儲空間但是并未找到給定條件的值時,仍然會觸發(fā)onsuccess函數(shù)。
同時,如果你想要限定你在游標中看到的值的范圍,你可以使用一個key range對象然后把它作為***個參數(shù)傳給openCursor或是openKeyCursor方法。你可以構(gòu)造一個只允許一個單一key的 key range,或者一個具有下限或上限,或者一個既有上限也有下限。邊界可以是閉合的(也就是說key range包含給定的值)或者是“開放的”(也就是說key range不包括給定的值)。具體實例如下:
- // 只匹配 'Donna'
- const singleKeyRange = IDBKeyRange.only('Donna');
- // 匹配所有在 'Bill' 前面的, 包括 'Bill'
- const lowerBoundKeyRange = IDBKeyRange.lowerBound('Bill');
- // 匹配所有在 “Bill” 前面的, 但是不需要包括 'Bill'
- const lowerBoundOpenKeyRange = IDBKeyRange.lowerBound('Bill', true);
- // Match anything up to, but not including, 'Donna'
- const upperBoundOpenKeyRange = IDBKeyRange.upperBound('Donna', true);
- //Match anything between 'Bill' and 'Donna', but not including 'Donna'
- const boundKeyRange = IDBKeyRange.bound('Bill', 'Donna', false, true);
- index.openCursor(boundKeyRange).onsuccess = function(event) {
- const cursor = event.target.result;
- if (cursor) {
- // Do something with the matches.
- cursor.continue();
- }
- };
IDBKeyRange對象的lowerBound和upperBound方法分別表示檢索指定key值前或者后的范圍(第二個參數(shù)指定邊界是否閉合,如果為true則不閉合)。
有時候你可能想要以倒序而不是正序(所有游標的默認順序)來遍歷。切換方向是通過傳遞prev到 openCursor方法來實現(xiàn)的。
- objectStore.openCursor(null, IDBCursor.prev).onsuccess = function(event) {
- const cursor = event.target.result;
- if (cursor) {
- // Do something with the entries.
- cursor.continue();
- }
- };
因為 “name” 索引不是***的,那就有可能存在具有相同 name 的多條記錄。要注意的是這種情況不可能發(fā)生在對象存儲空間上,因為鍵必須永遠是***的。如果你想要在游標在索引迭代過程中過濾出重復(fù)的,你可以傳遞 nextunique (或 prevunique 如果你正在向后尋找)作為方向參數(shù)。 當 nextunique 或是 prevunique 被使用時,被返回的那個總是鍵最小的記錄。
索引方法
在前面構(gòu)建數(shù)據(jù)庫時,我們創(chuàng)建了兩個索引?,F(xiàn)在,我們就通過這兩個索引來演示如何通過索引進行搜索。具體實例如下:
- const index = objectStore.index('by_name');
- //***種,使用get方法
- index.get(name).onsuccess = function(){
- alert(event.target.result.name);
- }
- //第二種,使用游標
- //普通游標
- index.openCursor().onsuccess = function(event) {
- const cursor = event.target.result;
- if (cursor) {
- // cursor.key 是一個 name, 就像 'Bill', 然后 cursor.value 是整個對象。
- alert('Name: ' + cursor.key + ', id' + cursor.value.id + ', email: ' + cursor.value.email);
- cursor.continue();
- }
- };
- //鍵游標
- index.openKeyCursor().onsuccess = function(event) {
- const cursor = event.target.result;
- if (cursor) {
- // cursor.key is 一個 name, 就像 'Bill', 然后 cursor.value 是那個id,即存儲對象的key值。
- alert('Name: ' + cursor.key + ', 'id: ' + cursor.value);
- cursor.continue();
- }
- };
異常處理
在瀏覽器有如下操作的情況下,indexedDB可能會出現(xiàn)異常:
- 用戶清除瀏覽器緩存
- 存儲空間超過大小限制
W3C規(guī)范文檔需要注意的應(yīng)用點
key值能夠接受的數(shù)據(jù)類型
在IndexedDB中,key值可以接受一下幾種類型的值:
- Number
- Date
- String
- ArrayBuffer
- Array
具體說明可以參考此處。
key path能夠接受的數(shù)據(jù)類型
在IndexedDB中,key path(主鍵)能夠接接受如下數(shù)據(jù)類型:
- Blob
- File
- Array
- StringI
注:空格不能出現(xiàn)在key path中。
value能夠接受的數(shù)據(jù)類型
在IndexedDB中,value能夠接受ECMA-262中所有的值,例如String,Date,ImageDate等。
事務(wù)中斷后,會不會影響key值的自增
IndexedDB在沒有指定key值的時候就會采用自增的key值,不受其他任何影響。如果一個事務(wù)在中途中斷,那么key值的自增將會從事務(wù)開始前的key開始。
IndexedDB的安全相關(guān)問題
IndexedDB也受到瀏覽器同源策略的限制。
總結(jié)
IndexedDB在本地存儲中有著無可替代的作用,是替代關(guān)系型數(shù)據(jù)庫websql的產(chǎn)品。在許多需要運用離線存儲的場景下,IndexedDB能夠給我們提供有效的支撐。但是,IndexedDB畢竟存在于客戶端,會出現(xiàn)諸如用戶清理緩存(Windows下更為常見)等破壞數(shù)據(jù)的情況,因此建議作為一個優(yōu)化用戶體驗的方案,不能過多依賴。
參考文獻
后續(xù)計劃
W3C的規(guī)范文檔還在學習中。在有時間的情況下,后續(xù)可能會嘗試翻譯IndexedDB 2.0的官方文檔,如果有需要的后續(xù)可以繼續(xù)關(guān)注。有什么問題也歡迎隨時交流。