IndexedDB是在客戶端的瀏覽器裏存儲大量數據的一個方案,網易雲信的IM也使用了IndexedDB來存儲客戶的本地消息,並且隨着PWA的興起,學會使用IndexedDB是必不可少的。本文主要將IndexedDB裏的一些主要概念抽解出來,幫助你們鞏固。javascript
好~ 你們能夠把瀏覽器的開發者工具打開,測試一下下面的示例。java
window.indexedDB
這個是在全局環境能夠訪問到的一個IndexedDB的工廠對象,在Console裏輸入 indexedDB
而後回車,就能夠看到這個對象是 IDBFactory
類。相似的, localStorage
是 Storage
類的實例對象, caches
是 CacheStorage
類的一個實例對象。sql
在console裏展開這個IDBFactory,展開 __proto__
,就能夠看到indexedDB這個對象的原型上有哪些方法了。
數據庫
咱們能夠經過 var request = indexDB.open("dbName", versionCode)
來打開一個存儲對象來存儲數據。數組
這個對象是indexdDB中對全部異步操做的請求對象,類比XMLHttpRequest對象,對請求的結果都須要監聽success事件。後文中凡是綁定onsuccess函數的都是在IDBRequest對象上進行,全部的返回結果都在該對象的result屬性上,因此能夠不用event.target.result來引用結果值,類比xhr.responseText屬性。
request.onerror/onupgradeneeded/onsuccess = function(event){}瀏覽器
request.onerror/onupgradeneeded/onsuccess = function(event){
db = event.target.result; // event.target.result === resust.result
// 這裏的db就是咱們的數據庫對象
}
複製代碼
IDBFactory.open
返回的是IDBOpenDBRequest
,繼承自IDBRequest
, 只有 IDBOpenDBRequest
上纔會有 onupgradeneeded
事件回調.在上面咱們調用open方法後,在request.result上能夠獲取到一個IDBDatabase對象,依然在console裏展開這個對象,以下圖。
數據結構
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
複製代碼
customers
的對象存儲空間,主鍵是ssn; 第一個參數是名稱 ,第二個參數是配置參數.再展開上面的objectStore對象,能夠看到下圖所示ObjectStore對象上的變量和方法。
異步
objectStore.createIndex("indexName", "keyPath", { unique: false });
複製代碼
到目前爲止,咱們能夠知道如何在建立數據庫或者更新數據庫時,對objectStore上的數據進行變動了。下面用代碼展現一下剛纔介紹的各類對象及操做方法。函數
const dbVersion = 1;// 默認的db版本號是0,若是你要傳入新的數據庫,那麼版本號必須大於0;
const dbName = 'test'
let db;
// IDBOpenReqeust對象
const openDBReq = window.indexedDB.open(dbName, dbVersion)
openDBReq.onerror = event => console.error(event)
// 建立數據庫或者數據庫版本變動時會觸發
openDBReq.onupgradeneeded = (event) => {
// 若是是數據庫版本變動,會有版本號
const { newVersion, oldVersion } = event;
// IDBDatabse對象
db = event.target.result;
// 在這個函數裏,咱們須要建立一個叫customer的表
let objectStore = db.createObjectStore('customers',{keyPath:'ssn'})
// 建立一個索引,便於快速查找數據
objectStore.createIndex(":)indexName", "name", { unique: false });
// 能夠在初始化的時候放入一些初始數據
let reqPut = objectStore.put({ssn:123,name:"yunxinim"})
reqPut.onerror = e => console.error('初始化時放入數據失敗')
reqPut.onsuccess = e => console.log('放置初始化數據成功')
}
openDBReq.onsuccess = event => db = event.target.result;
// 若是數據庫不使用了,能夠調用 openDBReq.close()關閉數據庫
複製代碼
那麼,在openDBReq.onsuccess後又怎樣操做objectStore呢? 咱們能夠調用IDBDatabse對象上 transaction
方法來操做ObjectStore,該方法會返回一個IDBTransaction對象。工具
在數據庫裏有個概念叫事務,一個事務執行過程當中若是錯誤,那麼在這個事務執行的過程當中所作的操做都會失效,這樣保證代碼執行到一半出錯時,不會對原來的數據形成影響。
var transaction = db.transaction(['customers', 'orders'],'readwrite');
複製代碼
你懂的,在console裏展開一下transaction,看看有啥屬性和方法。
事務和事件循環隊列的聯繫很是密切。若是你建立了一個事務可是並無使用它就返回了,那麼這個事務將會在下一個事件循環的時候就過時了,也就是說須要從新建立事務。保持事務活躍的惟一方法就是在其上構建一個請求(IDBRequest),這個請求會加入到事件循環隊列裏,這個事務也就保活了。當請求完成時(onsuccess)你將會獲得一個 DOM 事件,你能夠選擇在這個事件回調裏構建新的請求來延長事務。若是你沒有延長事務就返回到了事件循環,那麼事務將會過去,依此類推。只要還有待處理的請求,事務就會保持活躍。事務生命週期真的很簡單可是可能須要一點時間你才能對它變得習慣。若是你開始看到 TRANSACTION_INACTIVE_ERR
錯誤代碼,說明這個生命週期沒有續上。
PS: 當你要操做多個ObjectStore時,最好使用transaction來保證在失敗的狀況下不會修改數據。
PSS: onupgradeneeded事件裏( event.target
或 openDBReq
)上存在一個屬性transaction
,其實這是一個 versionchange
的事務,在數據庫版本變動的時候,若是你須要對舊版本數據進行遷移,那麼你可能會須要使用這個事務來延長 onupgradeneeded 時間.
下面用代碼來展現一下如何使用事務來操做ObjectStore
const dbVersion = 1;// 默認的db版本號是0,若是你要傳入新的數據庫,那麼版本號必須大於0;
const dbName = 'test'
let db;
// IDBOpenReqeust對象
const openDBReq = window.indexedDB.open(dbName, dbVersion)
openDBReq.onerror = event => console.error(event)
// 建立數據庫或者數據庫版本變動時會觸發
openDBReq.onupgradeneeded = (event) => {
// 若是是數據庫版本變動,會有版本號
const { newVersion, oldVersion } = event;
// IDBDatabse對象
db = event.target.result;
// 在這個函數裏,咱們須要建立一個叫customer的表
let objectStore = db.createObjectStore('customers',{keyPath:'ssn'})
// 建立一個索引,便於快速查找數據
objectStore.createIndex(":)indexName", "name", { unique: false });
// 能夠在初始化的時候放入一些初始數據
let reqPut = objectStore.put({ssn:123,name:"yunxinim"})
reqPut.onerror = e => console.error('初始化時放入數據失敗')
reqPut.onsuccess = e => console.log('放置初始化數據成功')
}
openDBReq.onsuccess = event => {
db = event.target.result;
// 建立一個讀寫customers裏的事務
const tx = db.transaction(['customers'],'readwrite');
let customers = tx.objectStore('customers')
customers.get(123).onsuccess = evt => {
let theCustomer = evt.target.result;
console.log('獲取數據', theCustomer)
theCustomer.phone = theCustomer.phone + 130;
customers.put(theCustomer).onsuccess = evt => {
console.log('更新數據成功', theCustomer)
}
}
}
複製代碼
咱們可使用遊標來遍歷ObjectStore裏的全部數據。
objectStore.openCursor().onsuccess = function(e){
var cursor = e.target.result;
if (cursor) {
alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
cursor.continue();
}
else {
alert("No more entries!");
}
}
複製代碼
openCursor() 函數須要幾個參數。首先,你可使用一個 key range對象來限制被檢索的項目的範圍。第二,你能夠指定你但願進行迭代的方向。在上面的示例中,咱們在以升序迭代全部的對象。
// IDBCursorWithValue 繼承自IDBCursor
{
direction:"next",
key:"444-44-4444",
primaryKey:"444-44-4444",
source: IDBObjectStore,
value: Object
}
複製代碼
若是要獲得全部的objectStore中的對象,可使用objectStore.getAll() .(ps:getAll方法可能有兼容性問題)
keyRage至關於sql裏的whereClause.
IDBIndex對象是索引,在建立ObjectStore時能夠爲一個屬性建立一個索引,便於快速查找。
使用索引: objectStore.index("indexName")
var index = objectStore.index(":)name");
// 這裏要使用在onupgradeneeded裏建立的索引的名稱,不是對象的name屬性名稱
index.get("yunxinim").onsuccess = function(event) {
alert("Donna's primaryKey is " + event.target.result.primaryKey);
};
複製代碼
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key 是一個 primaryKey,即createObjectStore時指定的keyPath屬性對應的值, 就像 "Bill",
// 而後 cursor.value 是整個對象。
alert("primary key: " + cursor.key + ", SSN: " + cursor.value.ssn + ", name: " + cursor.value.name);
cursor.continue();
}
};
index.openKeyCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key is 一個 當前索引的值,若是索引的是對象上的name,那麼cursor.key就是name,
// primaryKey是createObjectStore時指定的keyPath屬性對應的值,
// cursor.value 沒有值。
// 在這個cursor上沒有辦法能夠獲得存儲對象的其他部分。
alert("Name: " + cursor.key + ", value is undefined: " + cursor.value);
cursor.continue();
}
};
複製代碼
若是要從遊標上跳到指定次數的遊標處,可使用cursor.advance(Number biggerThan0)方法,直接前進指定次數的遊標。傳入的參數要求必須大於0。
若是你想要限定你在遊標中看到的值的範圍,你可使用一個 key range 對象而後把它做爲第一個參數傳給 openCursor()或是 openKeyCursor()。你能夠構造一個只容許一個單一 key 的 key range,或者一個具備下限或上限,或者一個既有上限也有下限。邊界能夠是閉合的(也就是說 key range
包含給定的值)或者是「開放的」(也就是說 **key range **不包括給定的值)。這裏是它如何工做的:
// 只匹配 "Donna"
var singleKeyRange = IDBKeyRange.only("Donna");
// 匹配全部在 "Bill" 前面的, 包括 "Bill"
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");
// 匹配全部在 「Bill」 前面的, 可是不須要包括 "Bill"
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);
// Match anything up to, but not including, "Donna"
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);
//Match anything between "Bill" and "Donna", but not including "Donna"
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
index.openCursor(boundKeyRange).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the matches.
cursor.continue();
}
};
複製代碼
有時候你可能想要以倒序而不是正序(全部遊標的默認順序)來遍歷。切換方向是經過傳遞 prev到 openCursor() 方法來實現的:
objectStore.openCursor(null, IDBCursor.prev).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the entries.
cursor.continue();
}
};
複製代碼
//新建objectStore時
db.createObjectStore("name",{
(DOMString or sequence<DOMString>)? keyPath = null;
boolean autoIncrement = false;
});
//新增`index`時
dictionary IDBIndexParameters {
boolean unique = false;
boolean multiEntry = false;
};
//除了upgradeneeded 還有 onblocked 是在另外的標籤頁打開數據庫時觸發
//該事件包含兩個參數
{
readonly attribute unsigned long long oldVersion;
readonly attribute unsigned long long? newVersion;
};
複製代碼
transaction打開objectStore有三個選項**readonly**
,**readwrite**
,**versionchange**
;
類型爲**versionchange**
的**transaction**
能夠修改**index**
,**keypath**
,可是這種類型的**transaction**
只能由**upgradeneeded**
事件生成.
indexDB的方法有 open, deleteDatabase, cmp**cmp**
比較傳入的參數,若是數值出錯,將拋出異常. 第一個數比第二個數大,返回1;比第二個數小,返回-1;相同,返回0;
Database的屬性:
readonly attribute DOMString name;
readonly attribute unsigned long long version;
readonly attribute DOMStringList objectStoreNames;
IDBObjectStore createObjectStore (DOMString name, optional IDBObjectStoreParameters optionalParameters);
void deleteObjectStore (DOMString name);
IDBTransaction transaction ((DOMString or sequence<DOMString>) storeNames, optional IDBTransactionMode mode = "readonly");
void close ();
複製代碼
直接open一個數據庫,若是數據庫不存在,將新建該數據庫,version爲0;
如何實現跨表查詢?
目前只有經過indexRange實現簡單的多條件查詢.跨表只能經過在遊標裏面循環. 建議修改本身存儲的數據結構,參考No-SQL數據庫的數據結構設計,存儲的值能夠有多層。
最後,原生的接口很難用,建議使用一些封裝好的庫,例如Dexie.js,idb等等。
參考:
[developer.mozilla.org/zh-CN/docs/…](developer.mozilla.org/zh-CN/docs/…)