什么是 indexedDB
IndexedDB 是一種使用瀏覽器存儲(chǔ)大量數(shù)據(jù)的方法.它創(chuàng)造的數(shù)據(jù)可以被查詢,并且可以離線使用. IndexedDB對(duì)于那些需要存儲(chǔ)大量數(shù)據(jù),或者是需要離線使用的程序是非常有效的解決方法. --- MDN
??上面是MDN上對(duì)于IndexedDB的介紹.其簡單而言,indexedDB就是一個(gè)基于事務(wù)操作的key-value型數(shù)前端數(shù)據(jù)庫.其API大多是異步的
?
創(chuàng)建一個(gè)indexedDB數(shù)據(jù)庫
const?request?=?indexedDB.open('myDatabase',?1);
request.addEventListener('success',?e?=>?{
????console.log('連接數(shù)據(jù)庫成功');
});
?
request.addEventListener('error',?e?=>?{
????console.log('連接數(shù)據(jù)庫失敗');
});
??在上面代碼中我們使用indexedDB.open()創(chuàng)建一個(gè)indexedDB數(shù)據(jù)庫.open()方法接受可以接受兩個(gè)參數(shù).第一個(gè)是數(shù)據(jù)庫名,第二個(gè)是數(shù)據(jù)庫的版本號(hào).同時(shí)返回一個(gè)IDBOpenDBRequest對(duì)象用于操作數(shù)據(jù)庫.其中對(duì)于open()的第一個(gè)參數(shù)數(shù)據(jù)庫名,open()會(huì)先去查找本地是否已有這個(gè)數(shù)據(jù)庫,如果有則直接將這個(gè)數(shù)據(jù)庫返回,如果沒有,則先創(chuàng)建這個(gè)數(shù)據(jù)庫,再返回.對(duì)于第二個(gè)參數(shù)版本號(hào),則是一個(gè)可選參數(shù),如果不傳,默認(rèn)為1.但如果傳入就必須是一個(gè)整數(shù).
??在通過對(duì)indexedDB.open()方法拿到一個(gè)數(shù)據(jù)庫對(duì)象IDBOpenDBRequest我們可以通過監(jiān)聽這個(gè)對(duì)象的success事件和error事件來執(zhí)行相應(yīng)的操作.
?
創(chuàng)建一個(gè)對(duì)象倉庫
??再有了一個(gè)數(shù)據(jù)庫之后,我們獲取就想要去存儲(chǔ)數(shù)據(jù)了,但是單只有數(shù)據(jù)庫還不夠,我們還需要有對(duì)象倉庫(object store).對(duì)象倉庫(object store)是indexedDB數(shù)據(jù)庫的基礎(chǔ),其類似于MySQL中表的概念.
要?jiǎng)?chuàng)建一個(gè)對(duì)象倉庫必須在upgradeneeded事件中,而upgradeneeded事件只會(huì)在版本號(hào)更新的時(shí)候觸發(fā).這是因?yàn)閕ndexedDB API中不允許數(shù)據(jù)庫中的數(shù)據(jù)倉庫在同一版本中發(fā)生變化
const?request?=?indexedDB.open('myDatabase',?2);
request.addEventListener('upgradeneeded',?e?=>?{
????const?db?=?e.target.result;
????const?store?=?db.createObjectStore('Users',?{
????????keyPath:?'userId',
????????autoIncrement:?false
????});
????console.log('創(chuàng)建對(duì)象倉庫成功');
});
??在上述代碼中我們監(jiān)聽upgradeneeded事件,并在這個(gè)事件觸發(fā)時(shí)使用createObjectStore()方法創(chuàng)建了一個(gè)對(duì)象倉庫.createObjectStore()方法接受兩個(gè)參數(shù),第一個(gè)是對(duì)象倉庫的名字,在同一數(shù)據(jù)庫中,倉庫名不能重復(fù).第二個(gè)是可選參數(shù).用于指定數(shù)據(jù)的主鍵,以及是否自增主鍵.
?
創(chuàng)建事務(wù)
??OK現(xiàn)在我們有了數(shù)據(jù)庫和對(duì)象倉庫了,我們是否就可以存儲(chǔ)數(shù)據(jù)了了.很抱歉,還是不行.我們還差最后一樣?xùn)|西----事務(wù).
?
什么是事務(wù)
一個(gè)數(shù)據(jù)庫事務(wù)通常包含了一個(gè)序列的對(duì)數(shù)據(jù)庫的讀/寫操作。它的存在包含有以下兩個(gè)目的
??上面是維基百科上對(duì)數(shù)據(jù)庫事務(wù)的解釋.簡單來說事務(wù)就是用來保證數(shù)據(jù)庫操作要么全部成功,要么全部失敗的一個(gè)限制.比如,在修改多條數(shù)據(jù)時(shí),前面幾條已經(jīng)成功了.,在中間的某一條是失敗了.那么在這時(shí),如果是基于事務(wù)的數(shù)據(jù)庫操作,那么這時(shí)數(shù)據(jù)庫就應(yīng)該重置前面數(shù)據(jù)的修改,放棄后面的數(shù)據(jù)修改.直接返回錯(cuò)誤,一條數(shù)據(jù)也不修改.
const?request?=?indexedDB.open('myDatabase',?3);
request.addEventListener('success',?e?=>?{
????const?db?=?e.target.result;
????const?tx?=?db.transaction('Users',?'readwrite');
});
上述代碼中我們使用transaction()來創(chuàng)建一個(gè)事務(wù).transaction()接受兩個(gè)參數(shù),第一個(gè)是你要操作的對(duì)象倉庫名稱,第二個(gè)是你創(chuàng)建的事務(wù)模式.傳入 readonly時(shí)只能對(duì)對(duì)象倉庫進(jìn)行讀操作,無法寫操作.可以傳入readwrite進(jìn)行讀寫操作.
?
操作數(shù)據(jù)
??好了現(xiàn)在有了數(shù)據(jù)庫,對(duì)象倉庫,事務(wù)之后我們終于可以存儲(chǔ)數(shù)據(jù)了.
-
add() : 增加數(shù)據(jù)。接收一個(gè)參數(shù),為需要保存到對(duì)象倉庫中的對(duì)象。
-
put() : 增加或修改數(shù)據(jù)。接收一個(gè)參數(shù),為需要保存到對(duì)象倉庫中的對(duì)象。
-
get() : 獲取數(shù)據(jù)。接收一個(gè)參數(shù),為需要獲取數(shù)據(jù)的主鍵值。
-
delete() : 刪除數(shù)據(jù)。接收一個(gè)參數(shù),為需要獲取數(shù)據(jù)的主鍵值。
add 和 put 的作用類似,區(qū)別在于 put 保存數(shù)據(jù)時(shí),如果該數(shù)據(jù)的主鍵在數(shù)據(jù)庫中已經(jīng)有相同主鍵的時(shí)候,則會(huì)修改數(shù)據(jù)庫中對(duì)應(yīng)主鍵的對(duì)象,而使用 add 保存數(shù)據(jù),如果該主鍵已經(jīng)存在,則保存失敗。
?
添加數(shù)據(jù)
const?request?=?indexedDB.open('myDatabase',?3);
request.addEventListener('success',?e?=>?{
????const?db?=?e.target.result;
????const?tx?=?db.transaction('Users',?'readwrite');
????const?store?=?tx.objectStore('Users');
????const?reqAdd?=?store.add({
????????'userId':?1,
????????'userName':?'李白',
????????'age':?24
????});
????reqAdd.addEventListener('success',?e?=>?{
????????console.log('保存成功')
????})
});
獲取數(shù)據(jù)
const?request?=?indexedDB.open('myDatabase',?3);
request.addEventListener('success',?e?=>?{
????const?db?=?e.target.result;
????const?tx?=?db.transaction('Users',?'readwrite');
????const?store?=?tx.objectStore('Users');
????const?reqGet?=?store.get(1);
????reqGet.addEventListener('success',?e?=>?{
????????console.log(this.result.userName);
????})
});
刪除數(shù)據(jù)
const?request?=?indexedDB.open('myDatabase',?3);
request.addEventListener('success',?e?=>?{
????const?db?=?e.target.result;
????const?tx?=?db.transaction('Users',?'readwrite');
????const?store?=?tx.objectStore('Users');
????const?reqDelete?=?store.delete(1);
????reqDelete.addEventListener('success',?e?=>?{
????????console.log('刪除數(shù)據(jù)成功');
????})
});
使用游標(biāo)
??在上面當(dāng)中我們使用get()方法傳入一個(gè)主鍵來獲取數(shù)據(jù),但是這樣只能夠獲取到一條數(shù)據(jù).如果我們想要獲取多條數(shù)據(jù)了怎么辦.我們可以使用游標(biāo),來獲取一個(gè)區(qū)間內(nèi)的數(shù)據(jù).
??要使用游標(biāo),我們需要使用對(duì)象倉庫上的openCursor()方法創(chuàng)建幣打開.openCursor()方法接受兩個(gè)參數(shù).
openCursor(range?:?IDBKeyRange?|?number?|?string?|?Date?|?IDBArrayKey,?direction?:?IDBCursorDirection):?IDBRequest;
??第一個(gè)是范圍,范圍可以是一個(gè)IDBKeyRange對(duì)象.用以下方式創(chuàng)建.
var?boundRange?=?IDBKeyRange.bound(1,?10,?false,?false);
var?onlyRange?=?IDBKeyRange.only(1);
var?lowerRange?=?IDBKeyRange.lowerBound(1,?false);
var?upperRange?=?IDBKeyRange.upperBound(10,?false);
??第二個(gè)參數(shù)是方向.主要有一下幾種
next : 游標(biāo)中的數(shù)據(jù)按主鍵值升序排列,主鍵值相等的數(shù)據(jù)都被讀取
nextunique : 游標(biāo)中的數(shù)據(jù)按主鍵值升序排列,主鍵值相等只讀取第一條數(shù)據(jù)
prev : 游標(biāo)中的數(shù)據(jù)按主鍵值降序排列,主鍵值相等的數(shù)據(jù)都被讀取
prevunique : 游標(biāo)中的數(shù)據(jù)按主鍵值降序排列,主鍵值相等只讀取第一條數(shù)據(jù)
??下面讓我們來看一個(gè)完整的例子
const?request?=?indexedDB.open('myDatabase',?4);
request.addEventListener('success',?e?=>?{
????const?db?=?e.target.result;
????const?tx?=?db.transaction('Users',?'readwrite');
????const?store?=?tx.objectStore('Users');
????const?range?=?IDBKeyRange.bound(1,?10);
????const?req?=?store.openCursor(range,?'next');
????req.addEventListener('success',?e?=>?{
????????const?cursor?=?this.result;
????????if?(cursor)?{
????????????console.log(cursor.value.userName);
????????????cursor.continue();
????????}?else?{
????????????console.log('檢索結(jié)束');
????????}
????})
});
??在上面的代碼中如果檢索到符合條件的數(shù)據(jù)時(shí),我們可以:
使用cursor.value拿到數(shù)據(jù).
使用cursor.updata()更新數(shù)據(jù).
使用cursor.delete()刪除數(shù)據(jù).
使用cursor.continue()讀取下一條數(shù)據(jù).
索引
??在上面代碼中我們獲取數(shù)據(jù)都是用的主鍵.但是,在很多情況下我們并不知道我們需要數(shù)據(jù)的主鍵是什么,我們知道一個(gè)大概的條件.比如說年齡大于20歲的用戶.這個(gè)時(shí)候我們就需要用到索引.以便有條件的查找.
創(chuàng)建索引
??我們使用對(duì)象倉庫的createIndex()方法來創(chuàng)建一個(gè)索引.
createIndex(name:?string,?keyPath:?string?|?string[],?optionalParameters?:?IDBIndexParameters):?IDBIndex;
createIndex()方法接收三個(gè)參數(shù):
-
第一個(gè)參數(shù)name是索引名,不能重復(fù).
-
第二個(gè)參數(shù)keyPath是你要在存儲(chǔ)對(duì)象上的那個(gè)屬性上建立索引,可以是一個(gè)單個(gè)的key值,也可以是一個(gè)包含key值集合的數(shù)組.
-
第三個(gè)參數(shù)optionalParameters是一個(gè)可選的對(duì)象參數(shù){unique, multiEntry}
-
unique: 用來指定索引值是否可以重復(fù),為true代表不能相同,為false時(shí)代表可以相同
-
multiEntry: 當(dāng)?shù)诙€(gè)參數(shù)keyPath為一個(gè)數(shù)組時(shí).如果multiEntry是true,則會(huì)以數(shù)組中的每個(gè)元素建立一條索引.如果是false,則以整個(gè)數(shù)組為keyPath值,添加一條索引.
??下面讓我們來看一個(gè)完整的例子,我們建立一條用戶年齡的索引.
const?request?=?indexedDB.open('myDatabase',?5);
request.addEventListener('upgradeneeded',?e?=>?{
????const?db?=?e.target.result;
????const?store?=?db.createObjectStore('Users',?{
????????keyPath:?'userId',
????????autoIncrement:?false
????});
????const?idx?=?store.createIndex('ageIndex',?'age',?{
????????unique:?false
????})
});
??這樣我們就創(chuàng)建了一條索引.
創(chuàng)建索引
??這在創(chuàng)建了一條索引之后我們就可以來使用它了.我們使用對(duì)象倉庫上的index方法,通過傳入一個(gè)索引名.來拿到一個(gè)索引對(duì)象.
const?index?=?store.index('ageIndex');`
??然后我們就可以使用這個(gè)索引了.比如說我們要拿到年齡在20歲以上的數(shù)據(jù),升序排列.
const?request?=?indexedDB.open('myDatabase',?4);
request.addEventListener('success',?e?=>?{
????const?db?=?e.target.result;
????const?tx?=?db.transaction('Users',?'readwrite');
????const?store?=?tx.objectStore('Users');
????const?index?=?store.index('ageIndex');
????const?req?=?index.openCursor(IDBKeyRange.lowerBound(20),?'next');
????req.addEventListener('success',?e?=>?{
????????const?cursor?=?e.target.result;
????????if?(cursor)?{
????????????console.log(cursor.value.age);
????????????cursor.continue();
????????}?else?{
????????????console.log('檢索結(jié)束');
????????}
????})
});
indexedDB 的兼容性
?
上面是我對(duì)indexedDB一些粗淺的總結(jié),希望對(duì)大家有所幫助.如果文中有何不當(dāng)之處請(qǐng)予以斧正,謝謝.
?
本文摘自 :https://blog.51cto.com/u