indexedDB 簡介:
indexedDB 是一種使用瀏覽器存儲大量數據的方法。它創造的數據可以被查詢,並且可以離線使用。
indexedDB 有以下特點:
-
indexedDB 是 WebSQL 數據庫的取代品
-
indexedDB遵循同源協議(只能訪問同域中存儲的數據,而不能訪問其他域的)
-
API包含異步API和同步API兩種:多數情況下使用異步API; 同步API必須同 WebWorkers 一起使用, 目前沒有瀏覽器支持同步API
-
indexedDB 是事務模式的數據庫, 使用 key-value 鍵值對儲存數據
-
indexedDB 不使用結構化查詢語言(SQL). 它通過索引(index)所產生的指針(cursor)來完成查詢操作
一、使用indexedDB的基本模式
-
打開數據庫並且開始一個事務。
-
創建一個 objecStore。
-
構建一個請求來執行一些數據庫操作,像增加或提取數據等。
-
通過監聽正確類型的 DOM 事件以等待操作完成。
-
在操作結果上進行一些操作(可以在 request 對象中找到)
二、創建、打開數據庫
indexedDB 存在於全局對象window上, 它最重要的一個方法就是open方法, 該方法接收兩個參數:
-
dbName // 數據庫名稱 [string]
-
version // 數據庫版本 [整型number]
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)中, 一個數據庫會有多個對象存儲空間,每個存儲空間有自己的主鍵、索引等;
創建對象存儲空間的操作一般放在創建數據庫成功回調裏:
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 版本變更
向數據庫中增加數據
前面提到,增加數據需要通過事務,事務的使用方式如下:
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('事務回滾!');
};
insert-data-web數據庫對象的transaction()方法接收兩個參數:
storeNames // 對象存儲空間,可以是對象存儲空間名稱的數組,也可以是單個對象存儲空間名稱,必傳 [array|string]
mode // 事務模式,上面提到的三種之一,可選,默認值是readonly [string]
這樣,我們得到一個事務對象transaction, 有三種事件可能會被觸發: complete, error, abort. 現在,我們通過事務向數據庫indexedDB-test的 對象存儲空間movies中插入數據:
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方法,同上類似:
var request =
db.transaction(['movies'], 'readwrite')
.objectStore('movies')
.delete('27054612'); // 通過鍵id來刪除
request.onsuccess = function(event) {
console.log('刪除成功!');
console.log(event.target.result);
};
從數據中獲取數據
獲取數據使用get方法,同上類似:
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/的對象,就可以使用索引。
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重複:
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值爲尋夢環遊記的對象:
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()
在對象存儲空間上使用遊標
使用遊標常見的一種模式是獲取對象存儲空間上的所有數據.
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()方法,可以獲取所有對象:
objectStore.getAll().onsuccess = function(event) {
console.log('result:', event.target.result);
};
在索引上使用遊標
接着本文上述使用索引的例子,在索引title上使用openCursor()方法時,若不傳參數,則會遍歷所有數據,在成功回調中的到的result對象有以下屬性:
key 數據庫中這條對象的title屬性值
primaryKey 數據庫中這條對象的alt值
value 數據庫中這條對象
direction openCursor()方法傳入的第二個對象,默認值爲next
source IDBIndex對象 舉例如下:
var 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的一些方法設置該值:
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其他一些方法:
// 匹配所有在 "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
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 被使用時,被返回的那個總是鍵最小的記錄。
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()方法即可
db.close();
關閉數據庫後,db對象仍然保存着該數據庫的相關信息,只是無法再開啓事務(調用開啓事務方法會報錯,提示數據庫連接已斷開):
刪除數據庫則需要使用indexedDB.deleteDatabase(dbName)方法
window.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)
參考
IndexedDB的基本概念-MDN
使用 IndexedDB-MDN
IndexedDB API接口-MDN
Indexed Database API 2.0 – w3c