indexedDB
簡介:
indexedDB
是一種使用瀏覽器儲存大量資料的方法.它創造的資料可以被查詢,並且可以離線使用.
indexedDB
有以下特點:
indexedDB
是WebSQL
資料庫的取代品indexedDB
遵循同源協議(只能訪問同域中儲存的資料,而不能訪問其他域的)API
包含非同步API和同步API兩種:多數情況下使用非同步API; 同步API必須同 WebWorkers 一起使用, 目前沒有瀏覽器支援同步APIindexedDB
是事務模式的資料庫, 使用key-value
鍵值對儲存資料indexedDB
不使用結構化查詢語言(SQL
). 它通過索引(index
)所產生的指標(cursor
)來完成查詢操作
一、使用indexedDB的基本模式
- 開啟資料庫並且開始一個事務。
- 建立一個
objecStore
。 - 構建一個請求來執行一些資料庫操作,像增加或提取資料等。
- 通過監聽正確型別的
DOM
事件以等待操作完成。 - 在操作結果上進行一些操作(可以在
request
物件中找到)
二、建立、開啟資料庫
indexedDB
存在於全域性物件window
上, 它最重要的一個方法就是open
方法, 該方法接收兩個引數:
dbName
// 資料庫名稱[string]
version
// 資料庫版本[整型number]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var DB_NAME = 'indexedDB-test', VERSION = 1, db; var request = indexedDB.open(DB_NAME, VERSION); request.onsuccess = function(event) { db = event.target.result; // console.log(event.target === request); // true db.onsuccess = function(event) { console.log('資料庫操作成功!'); }; db.onerror = function(event) { console.error('資料庫操作發生錯誤!', event.target.errorCode); }; console.log('開啟資料庫成功!'); }; request.onerror = function(event) { console.error('建立資料庫出錯'); console.error('error code:', event.target.errorCode); }; request.onupgradeneeded = function(event) { // 更新物件儲存空間和索引 .... }; |
若是本域下不存在名為DB_NAME
的資料庫,則上述程式碼會建立一個名為DB_NAME
、版本號為VERSION
的資料庫; 觸發的事件依次為: upgradeneeded
、 success
.
若是已存在名為DB_NAME
的資料庫, 則上述程式碼會開啟該資料庫; 只觸發success
/error
事件,不會觸發upgradeneeded
事件. db
是對該資料庫的引用.
三、建立物件儲存空間和索引
在關係型資料庫(如mysql)中,一個資料庫中會有多張表,每張表有各自的主鍵、索引等;
在key-value
型資料庫(如indexedDB)中, 一個資料庫會有多個物件儲存空間,每個儲存空間有自己的主鍵、索引等;
建立物件儲存空間的操作一般放在建立資料庫成功回撥裡:
1 2 3 4 5 6 |
request.onupgradeneeded = function(event) { // 更新物件儲存空間和索引 .... var database = event.target.result; var objectStore = database.createObjectStore("movies", { keyPath: "id" }); objectStore.createIndex('alt', 'alt', { unique: true }); objectStore.createIndex('title', 'title', { unique: false }); }; |
onupgradeneeded
是我們唯一可以修改資料庫結構的地方。在這裡面,我們可以建立和刪除物件儲存空間以及構建和刪除索引。
在資料庫物件database
上,有以下方法可供呼叫:
createObjectStore(storeName, configObj)
建立一個物件儲存空間storeName
// 物件儲存空間的名稱[string]
configObj
// 該物件儲存空間的配置[object]
(其中的keyPath屬性值,標誌物件的該屬性值唯一)
createIndex(indexName, objAttr, configObj)
建立一個索引indexName
// 索引名稱[string]
objAttr
// 物件的屬性名[string]
configObj
// 該索引的配置物件[object]
四、增加和刪除資料
對資料庫的操作(增刪查改等)都需要通過事務來完成,事務具有三種模式:
readonly
只讀(可以併發進行,優先使用)readwrite
讀寫versionchange
版本變更
向資料庫中增加資料
前面提到,增加資料需要通過事務,事務的使用方式如下:
1 2 3 4 5 6 7 8 9 10 |
var transaction = db.transaction(['movies'], 'readwrite'); transaction.oncomplete = function(event) { console.log('事務完成!'); }; transaction.onerror = function(event) { console.log('事務失敗!', event.target.errorCode); }; transaction.onabort = function(event) { console.log('事務回滾!'); }; |
storeNames
// 物件儲存空間,可以是物件儲存空間名稱的陣列,也可以是單個物件儲存空間名稱,必傳[array|string]
mode
// 事務模式,上面提到的三種之一,可選,預設值是readonly
[string]
這樣,我們得到一個事務物件transaction
, 有三種事件可能會被觸發: complete
, error
, abort
. 現在,我們通過事務向資料庫indexedDB-test
的 物件儲存空間movies
中插入資料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var objectStore = transaction.objectStore('movies'); // 指定物件儲存空間 var data = [{ "title": "尋夢環遊記", "year": "2017", "alt": "https://movie.douban.com/subject/20495023/", "id": "20495023" }, { "title": "你在哪", "year": "2016", "alt": "https://movie.douban.com/subject/26639033/", "id": "26639033" }, { "title": "筆仙咒怨", "year": "2017", "alt": "https://movie.douban.com/subject/27054612/", "id": "27054612" }]; data.forEach(function(item, index){ var request = objectStore.add(item); request.onsuccess = function(event) { console.log('插入成功!', index); console.log(event.target.result, item.id); // add()方法呼叫成功後result是被新增的值的鍵(id) }; }); |
通過事務物件transaction
,在objectStore()
方法中指定物件儲存空間,就得到了可以對該物件儲存空間進行操作的物件objectStore
.
向資料庫中增加資料,add()
方法增加的物件,若是資料庫中已存在相同的主鍵,或者唯一性索引的鍵值重複,則該條資料不會插入進去;
增加資料還有一個方法: put()
, 使用方法和add()
不同之處在於,資料庫中若存在相同主鍵或者唯一性索引重複,則會更新該條資料,否則插入新資料。
從資料庫中刪除資料
刪除資料使用delete
方法,同上類似:
1 2 3 4 5 6 7 8 |
var request = db.transaction(['movies'], 'readwrite') .objectStore('movies') .delete('27054612'); // 通過鍵id來刪除 request.onsuccess = function(event) { console.log('刪除成功!'); console.log(event.target.result); }; |
從資料中獲取資料
獲取資料使用get
方法,同上類似:
1 2 3 4 5 6 7 |
var request = db.transaction('movies') .objectStore('movies') .get('9999682'); // 通過鍵alt來獲取 request.onsuccess = function(event) { console.log('獲取成功!', event.target.result); }; |
五、使用索引
在前面,我們建立了兩個索引alt
和title
, 配置物件裡面的unique
屬性標誌該值是否唯一
現在我們想找到alt
屬性值為https://movie.douban.com/subject/26639033/
的物件,就可以使用索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var alt = 'https://movie.douban.com/subject/26639033/'; var objectStore = db.transaction('movies').objectStore('movies'); // 開啟物件儲存空間 var index = objectStore.index('alt'); // 使用索引'alt' var request = index.get(alt); // 建立一個查詢資料的請求 request.onsuccess = function(event) { console.log('The result is:', event.target.result); }; var noDataTest = index.get('testalt'); // 沒有該物件時的測試 noDataTest.onsuccess = function(event) { console.log('success! result:', event.target.result); }; noDataTest.onerror = function(event) { console.log('error! event:', event); }; |
使用唯一性索引,我們可以得到唯一的一條資料(或者undefined
),那麼使用非唯一性索引呢?
我們向資料庫中插入一條資料,使title
重複:
1 2 3 4 5 6 7 |
db.transaction('movies', 'readwrite').objectStore('movies') .add({ alt: 'https://movie.douban.com/subject/27054612121/', title: '尋夢環遊記', year: '2017', id: '123456789' }) .onsuccess = function(event) { console.log('插入成功!'); }; |
使用索引title
獲取title
值為尋夢環遊記的物件:
1 2 3 4 5 6 7 |
var indexName = 'title', title = '尋夢環遊記'; var objectStore = db.transaction('movies').objectStore('movies'); var index = objectStore.index(indexName); // 使用索引'alt' var request = index.get(title); // 建立一個查詢資料的請求 request.onsuccess = function(event) { console.log('The result is:', event.target.result); }; |
我們得到的是鍵值最小的那個物件.
使用一次索引,我們只能得到一條資料; 如果我們需要得到所有title
屬性值為尋夢環遊記
的物件,我們可以使用遊標.
六、使用遊標
得到一個可以操作遊標的請求物件有兩個方法:
openCursor(keyRange, direction)
openKeyCursor(keyRange, direction)
這兩個方法接收的引數一樣, 兩個引數都是可選的: 第一個引數是限制值得範圍,第二個引數是指定遊標方向
遊標的使用有以下幾處:
- 在物件儲存空間上使用:
var cursor = objectStore.openCursor()
- 在索引物件上使用:
var cursor = index.openCursor()
在物件儲存空間上使用遊標
使用遊標常見的一種模式是獲取物件儲存空間上的所有資料.
1 2 3 4 5 6 7 8 9 10 11 12 |
var list = []; var objectStore = db.transaction('movies').objectStore('movies'); objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor:', cursor); list.push(cursor.value); cursor.continue(); } else { console.log('Get all data:', list); } }; |
使用遊標時,需要在成功回撥裡拿到result
物件,判斷是否取完了資料:若資料已取完,result
是undefined
; 若未取完,則result
是個IDBCursorWithValue
物件,需呼叫continue()
方法繼續取資料。 也可以根據自己需求, 對資料進行過濾。
在indexedDB2
規範中,在物件儲存空間物件上納入了一個getAll()
方法,可以獲取所有物件:
1 2 3 |
objectStore.getAll().onsuccess = function(event) { console.log('result:', event.target.result); }; |
在索引上使用遊標
接著本文上述使用索引的例子,在索引title
上使用openCursor()
方法時,若不傳引數,則會遍歷所有資料,在成功回撥中的到的result
物件有以下屬性:
key
資料庫中這條物件的title
屬性值primaryKey
資料庫中這條物件的alt
值value
資料庫中這條物件direction
openCursor()方法傳入的第二個物件,預設值為next
source
IDBIndex物件 舉例如下:
12345678910var index = db.transaction('movies').objectStore('movies').index('title');index.openCursor().onsuccess = function(event) {var cursor = event.target.result;if (cursor) {console.log('cursor:', cursor);cursor.continue();}};
在索引title
上使用openKeyCursor()
方法,若不傳引數,同樣也會遍歷所有資料,result
物件屬性如下:
key
資料庫中這條物件的title
屬性值primaryKey
資料庫中這條物件的alt
值direction
openCursor()方法傳入的第二個物件,預設值為next
source
altBIndex物件
和openCursor()
方法相比,得到的資料少一個value
屬性,是沒有辦法得到儲存物件的其餘部分
前面說到,我們要根據索引title
獲取所有title
屬性值為尋夢環遊記
的物件,要使用遊標,而又不想遍歷所有資料,這時就要用到openCursor()
的第一個引數: keyRange
keyRange
是限定遊標遍歷的資料範圍,通過IDBKeyRange
的一些方法設定該值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var singleKeyRange = IDBKeyRange.only("尋夢環遊記"), list = []; var index = db .transaction('movies') .objectStore('movies').index('title'); index.openCursor(singleKeyRange).onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value); cursor.continue(); } else { console.log('list:', list); } }; |
IDBKeyRange
其他一些方法:
1 2 3 4 5 6 7 8 9 10 11 |
// 匹配所有在 "Bill" 前面的, 包括 "Bill" var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill"); // 匹配所有在 “Bill” 前面的, 但是不需要包括 "Bill" var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true); // 匹配所有在'Donna'後面的, 但是不包括"Donna" var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true); // 匹配所有在"Bill" 和 "Donna" 之間的, 但是不包括 "Donna" var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true); |
更多請參考 MDN|IDBKeyRange
遊標預設遍歷方向是按主鍵從小到大,有時候我們倒序遍歷,此時可以給openCursor()
方法傳遞第二個引數: direction
: next
|nextunique
|prev
|prevunique
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var singleKeyRange = IDBKeyRange.only("尋夢環遊記"), list = []; var index = db .transaction('movies') .objectStore('movies').index('title'); index.openCursor(singleKeyRange, 'prev').onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value); cursor.continue(); } else { console.log('list:', list); } }; |
傳了prev
的結果是按倒序遍歷的.
因為 “name” 索引不是唯一的,那就有可能存在具有相同 name 的多條記錄。 要注意的是這種情況不可能發生在物件儲存空間上,因為鍵必須永遠是唯一的。 如果你想要在遊標在索引迭代過程中過濾出重複的,你可以傳遞
nextunique
(或prevunique
, 如果你正在向後尋找)作為方向引數。 當nextunique
或是prevunique
被使用時,被返回的那個總是鍵最小的記錄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var singleKeyRange = IDBKeyRange.only("尋夢環遊記"), list = []; var index = db .transaction('movies') .objectStore('movies').index('title'); index.openCursor(singleKeyRange, 'prevunique').onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value); cursor.continue(); } else { console.log('list:', list); } }; |
七、關閉和刪除資料庫
- 關閉資料庫只需要在資料庫物件
db
上呼叫close()
方法即可
1db.close();
關閉資料庫後,db
物件仍然儲存著該資料庫的相關資訊,只是無法再開啟事務(呼叫開啟事務方法會報錯,提示資料庫連線已斷開):
- 刪除資料庫則需要使用
indexedDB.deleteDatabase(dbName)
方法
1window.indexedDB.deleteDatabase(dbName);
八、indexedDB的侷限性
以下情況不適合使用IndexedDB
- 全球多種語言混合儲存。國際化支援不好。需要自己處理。
- 和伺服器端資料庫同步。你得自己寫同步程式碼。
- 全文搜尋。
注意,在以下情況下,資料庫可能被清除:
- 使用者請求清除資料。
- 瀏覽器處於隱私模式。最後退出瀏覽器的時候,資料會被清除。
- 硬碟等儲存裝置的容量到限。
- 不正確的
- 不完整的改變.
總結
- 使用
indexedDB.open(dbName, version)
開啟一個資料庫連線 - 使用
indexedDB.deleteDatabase(dbName)
刪除一個資料庫 - 在資料庫物件
db
上使用createObjectStore(storeName, config)
建立物件儲存空間 - 在物件儲存空間
objectStore
上使用createIndex(indexName, keyName, config)
建立索引 - 對資料庫的操作都需要通過事務完成:
var transction = db.transaction([storeName], mode)
- 資料庫的增刪改查均通過
objectStore
物件完成,var objectStore = transaction.objectStore(storeName)
- 對資料庫資料操作有:
add()
、get()
、delete()
、put
等方法 - 查詢資料可以使用索引:
objectStore.index(indexName)
- 遍歷和過濾資料可以使用遊標:
openCursor(keyRange, direction)