客戶端持久化解決方案:indexedDB
indexedDB適合大量的結構化的數據存儲;打開數據庫和獲取數據對象都是異步的;
需要開啟事務,訪問的objectStore都要是在開啟的事務中。
數據庫結構: db->transaction->objectStore->data
Web SQL Database
實際上已經被廢棄,而HTML5支持的本地存儲實際上變成了 Web Storage
(Local Storage和Session Storage)與 IndexedDB
。
Web Storage
使用簡單字符串鍵值對在本地存儲數據,方便靈活,但是對於大量結構化數據存儲力不從心,IndexedDB
是為了能夠在客戶端存儲大量的結構化數據,並且使用索引高效檢索的API。
indexedDB最顯著的特點: 異步API
在IndexedDB大部分操作(如:打開數據庫和獲取數據)並不是同步的,如:
var request=window.indexedDB.open('testDB');
這條指令並不會返回一個DB對象的句柄,我們得到的是一個IDBOpenDBRequest對象,而我們希望得到的DB對象在IDBOpenDBRequest.result
屬性中.
indexedDB的常用操作
創建/打開數據庫
function openDB (name) {
var idbRequest=window.indexedDB.open(name);
idbRequest.onerror=function(e){
console.log('OPen Error!');
};
idbRequest.onsuccess=function(e){
var db=e.target.result;
console.log('db: %o', db);
};
}
openDB(dbName);
除了onerror和onsuccess,IDBOpenDBRequest還有一個類似回調函數句柄 onupgradeneeded
。這個句柄在我們請求打開的數據庫的版本號和已經存在的數據庫版本號不一致的時候調用。
// indexedDB.open(dbName, version);
function openDB (name, version) {
version = version || 1;
//打開或創建數據庫
var idbRequest = window.indexedDB.open(name, version);
idbRequest.onerror = function(e){
console.warn('error: %s', e.currentTarget.error.message);
};
idbRequest.onsuccess = function(e){
db = e.target.result; //這里才是 indexedDB對象
console.log('idbRequest === e.target: %o', idbRequest === e.target);
console.log('db: %o, idbRequest: %o', db, idbRequest);
};
// 注意: 只能請求>=當前數據庫版本號的版本
// 大於當前版本號, 則觸發 onupgradeneeded,
// 小於當前版本號,則觸發 onerror
idbRequest.onupgradeneeded = function(e){
console.log('DB version change to ' + version);
var db = e.target.result;
console.log('onupgradeneeded: db->', db);
};
}
刪除數據庫
window.indexedDB.deleteDatabase(dbName);
關閉數據庫
db.onclose = function(){
//do sth..
};
db.close();
創建 objectStore
有了數據庫后我們自然希望創建一個表用來存儲數據,但indexedDB中沒有表的概念,而是叫 objectStore
,一個數據庫中可以包含多個objectStore
,objectStore
是一個靈活的數據結構,可以存放多種類型數據。也就是說一個objectStore
相當於一張表,里面存儲的每條數據和一個鍵相關聯。
我們可以使用每條記錄中的某個指定字段作為鍵值(keyPath
如: {keyPath: 'id'} ),也可以使用自動生成的遞增數字作為鍵值(keyGenerator
如: {autoincrement: true} kk:很像mysql autoincrement字段),也可以不指定。
鍵類型 | 存儲數據 |
---|---|
不使用 | 任意值,但是每添加一條數據的時候,需指定鍵參數 |
keyPath | 對象,eg: {keyPath: 'id'} |
keyGenerator | 任意值 eg: {autoincrement: true} |
keyPath and KeyGenerator 都使用 | 對象,如果對象中有keyPath指定的屬性則不生成新的鍵值,如果沒有自動生成遞增鍵值,填充keyPath指定的屬性 |
事務
在對新數據庫做任何事情之前,需要開始一個事務。事務中需要指定該事務跨越哪些 objectStore.
事務具有三種模式:
-
只讀:read,不能修改數據庫數據,可以並發執行
-
讀寫:readwrite,可以進行讀寫操作
-
版本變更:verionchange
//打開一個事務,使用students 和teacher objectStore
var transaction=db.transaction([students','taecher']);
//獲取students objectStore
var objectStore=transaction.objectStore('students');//創建objectStore
db.createObjectStore(storeName, keyPath);
因為對新數據的操作都需要在transaction中進行,而transaction又要求指定objectStore,所以我們只能在創建數據庫的時候初始化objectStore以供后面使用,這正是onupgradeneeded的一個重要作用。
function openDB (name,version) {
var version=version || 1;
var idbRequest=window.indexedDB.open(name,version);
idbRequest.onerror=function(e){
console.log(e.currentTarget.error.message);
};
idbRequest.onsuccess=function(e){
myDB.db=e.target.result;
};
idbRequest.onupgradeneeded=function(e){
var db=e.target.result;
if(!db.objectStoreNames.contains('students')){
//db.createObjectStore('students',{autoIncrement: true});//keyGenerator
db.createObjectStore('students',{keyPath:"id"});
}
console.log('DB version changed to '+version);
};
}
刪除objectStore
db.deleteObjectStore(storeName); //注意:需在onupgradeneeded鈎子中執行
保存數據到objectStore
function saveData (dbName, version, storeName, data) {
var idbRequest = indexedDB.open(dbName, version);
idbRequest.onsuccess = function (e) {
var db = idbRequest.result;
var transaction = db.transaction(storeName, 'readwrite');//需先創建事務
var store = transaction.objectStore(storeName); //訪問事務中的objectStore
data.forEach(function (item) {
store.add(item);//保存數據
});
console.log('save data done..');
}
}
查找數據
function getDataByKey(db,storeName,key){
var transaction=db.transaction(storeName,'readwrite');
var store=transaction.objectStore(storeName);
var dataRequest=store.get(key);
dataRequest.onsuccess=function(e){//異步的
var student=e.target.result;
console.log(student.name);
};
}
更新數據
可以調用objectStore的put方法更新數據,會自動替換鍵值相同的記錄,達到更新目的,沒有相同的則添加。
function updateDataByKey(db,storeName,student){
var transaction=db.transaction(storeName,'readwrite');
var store=transaction.objectStore(storeName);
store.put(student);
}
刪除數據
function deleteDataByKey(db,storeName,key){
var transaction=db.transaction(storeName,'readwrite');
var store=transaction.objectStore(storeName);
store.delete(key);
}
清空數據
function clearObjectStore(db,storeName){
var transaction=db.transaction(storeName,'readwrite');
var store=transaction.objectStore(storeName);
store.clear();
}
indexedDB的索引
熟悉數據庫的同學都知道索引的一個好處就是可以迅速定位數據,提高搜索速度,在indexedDB中有兩種索引,一種是自增長的int值,一種是keyPath:自己指定索引列,我們重點來看看keyPath方式的索引使用.
創建索引
我們可以在db.createObjectStore(storeName, keyPath)
時用 store.createIndex(indexName, key, opts)
來指明索引。
function openDB (name,version) {
var version=version || 1;
var request=window.indexedDB.open(name,version);
request.onerror=function(e){
console.log(e.currentTarget.error.message);
};
request.onsuccess=function(e){
myDB.db=e.target.result;
};
request.onupgradeneeded=function(e){
var db=e.target.result;
if(!db.objectStoreNames.contains('students')){
var store=db.createObjectStore('students',{keyPath: 'id'});
//在students 上創建了兩個索引
store.createIndex('nameIndex','name',{unique:true});
store.createIndex('ageIndex','age',{unique:false});
}
console.log('DB version changed to '+version);
};
}
利用索引快速獲取數據,name的索引是唯一的沒問題,但是對於age索引只會取到第一個匹配值,要想得到所有age符合條件的值就需要使用游標了
function getDataByIndex(db,storeName){
var transaction=db.transaction(storeName);
var store=transaction.objectStore(storeName);
var index = store.index("nameIndex");//獲取索引
index.get('Byron').onsuccess=function(e){//通過索引獲取數據
var student=e.target.result;
console.log(student.id);
}
}
游標
在indexedDB中使用索引和游標是分不開的,對數據庫熟悉的同學很好理解游標是什么東東,有了數據庫objectStore的游標,我們就可以利用游標遍歷objectStore了。
objectStore.openCursor(); //打開游標
function fetchStoreByCursor(db,storeName){
var transaction=db.transaction(storeName);
var store=transaction.objectStore(storeName);
var request=store.openCursor();
request.onsuccess=function(e){
var cursor=e.target.result;
if(cursor){
console.log(cursor.key);
var currentStudent=cursor.value;
console.log(currentStudent.name);
cursor.continue();
}
};
}
index與游標結合
要想獲取age為26的student,可以結合游標使用索引
function getMultipleData(db,storeName){
var transaction=db.transaction(storeName);
var store=transaction.objectStore(storeName);
var index = store.index("ageIndex");
var request=index.openCursor(IDBKeyRange.only(26))
request.onsuccess=function(e){
var cursor=e.target.result;
if(cursor){
var student=cursor.value;
console.log(student.id);
cursor.continue();
}
}
}
這樣我們可是使用索引打開一個游標,在成功的句柄內獲得游標遍歷age為26的student,也可以通過index.openKeyCursor()方法只獲取每個對象的key值。
指定游標范圍
index.openCursor()/index.openKeyCursor()
方法在不傳遞參數的時候會獲取objectStore所有記錄,像上面例子一樣我們可以對搜索進行篩選
可以使用 IDBKeyRange 限制游標中值的范圍,把它作為第一個參數傳給 openCursor() 或是 openKeyCursor()
-
IDBKeyRange.only(value):只獲取指定數據
-
IDBKeyRange.lowerBound(value,isOpen):獲取最小是value的數據,第二個參數用來指示是否排除value值本身,也就是數學中的是否是開區間
-
IDBKeyRange.upperBound(value,isOpen):和上面類似,用於獲取最大值是value的數據
-
IDBKeyRange.bound(value1,value2,isOpen1,isOpen2):不用解釋了吧
// 獲取名字首字母在B-E的student
function getMultipleData(db,storeName){
var transaction=db.transaction(storeName);
var store=transaction.objectStore(storeName);
var index = store.index("nameIndex");
var request=index.openCursor(IDBKeyRange.bound('B','F',false, true ));
request.onsuccess=function(e){
var cursor=e.target.result;
if(cursor){
var student=cursor.value;
console.log(student.name);
cursor.continue();
}
}
}
有了游標和索引才能真正發揮indexedDB威力