indexedDB用於在瀏覽器端存儲大量的結構化數據。對比於其餘的瀏覽器存儲技術(cookie,localStorage),indexedDB具備如下優勢:數據庫
1.存儲空間特別大,遠超cookie與localStorage; 2.能夠經過索引實現高性能搜索; 3.全部操做徹底異步執行;
另外,indexedDB還具有如下特色:數組
1.非關係型數據庫,不能使用結構化查詢語言(SQL); 2.數據以健值對的方式存儲; 3.事務模式數據庫,任何操做都發生在事務中; 4.遵循同源策略;
基於以上的特色,indexedDB很是適合於須要在客戶端存儲大量數據的網站,更是和基於PWA的WebAPP相契合。
關於indexedDB的瀏覽器兼容性,能夠參照:https://www.caniuse.com/#search=indexeddb瀏覽器
var request = window.indexedDB.open(DBName, version);
函數包含兩個參數,第一個爲數據庫名稱,第二個爲數據庫版本號(整數)。
使用open
函數建立/打開數據庫時,根據參數不一樣,可能存在如下幾種狀況:緩存
1.指定名稱的數據庫不存在,則新建數據庫,默認版本號爲1; 2.指定名稱的數據庫已存在,省略版本號,則打開當前版本號的數據庫; 3.指定名稱的數據庫已存在,版本號大於數據庫實際版本號,則進行數據庫升級;
open
函數返回一個IDBOpenDBRequest
對象,該對象經過如下三種事件進行數據庫打開操做的後續處理:服務器
var db; //打開數據庫成功事件,返回數據庫對象實例(IDBDatabase對象的實例) request.onsuccess = function(event){ db = request.result; } //打開數據庫失敗事件 request.onerror = function(event){ //打印報錯信息 } //數據庫升級事件,僅在建立數據庫/數據庫升級時觸發 request.onupgradeneeded = function(event){ //緩存IDBDatabase接口,用於後續建立/刪除對象存儲空間(對象倉庫,類比於關係型數據庫的表) var _db = event.target.result; //建立/刪除對象存儲空間的操做 ... }
不管是打開數據庫,或者是更新數據庫,全部後續的查詢、插入、刪除等操做,都要經過onsuccess函數返回的IDBDatabase實例去調用。
全部對數據庫schema的更新只能在onupgradeneeded事件中進行。cookie
indexedDB中,使用對象倉庫來存儲具體的數據,其功能相似與關係型數據庫的表。
不一樣於關係型數據庫的表結構,對象倉庫中數據的存儲結構以下圖所示:
Key表明對象倉庫主鍵,而具體的數據,採用對象的方式存儲在Value中。異步
在數據庫新建操做完成後,接下來就是新建對象倉庫,並建立索引了。
對象倉庫的建立 與 該對象倉庫中索引的建立方法爲:數據庫設計
request.onupgradeneeded = function(event){ var _db = event.target.result; var objectStore; //首先,能夠先判斷是否存在重名的對象倉庫 if(!_db.objectStoreNames.contains('person')){ //建立對象倉庫,第一個參數爲倉庫名稱,第二個參數可選 objectStore = _db.createObjectStore('person', { //主鍵 keyPath: 'id', //可選,主鍵自增 autoIncrement: true }); //建立索引,第一個參數爲索引名稱,第二個參數爲索引所在的屬性,可使用數組將多個屬性設置爲一個索引 //第三個參數爲可選參數,指定該屬性的值是否要求惟一 objectStore.createIndex('phone_index', 'phone', {unique: true}); } }
關於爲何要建立索引:對數據庫不是很熟悉,或者對關係型數據庫有所瞭解,而對非關係型數據庫不太瞭解的同窗,可能會對索引有所疑惑。索引的意義,在於快速查詢數據,就如同書的目錄同樣,經過目錄能夠快速找到咱們想看內容的頁數。在關係型數據庫中,查詢數據,可使用SQL語句:select * from person where phone="13910002000",而在非關係型數據庫中,沒有SQL,若是沒有索引,咱們只能根據主鍵去查詢數據。只有創建索引以後,才能將索引對應的屬性做爲查詢條件進行數據查詢。
進行數據庫版本升級的狀況下,以前版本的對象倉庫是依然存在的,無需重複建立。若是須要更新對象倉庫屬性,須要先刪除原有對象倉庫,再從新建立。函數
indexedDB是事務模式數據庫,全部的數據操做,都要在事務中進行。所以在學習數據的增刪改查操做以前,咱們先看如何新建事務,並獲取IDBObjectStore對象。性能
1.建立事務
var transaction = db.transaction(storeNames, mode);
使用IDBDatabase對象實例來調用事務建立函數,第一個參數,能夠是一個對象倉庫名稱的字符串(僅使用到一個對象倉庫),或者是一個對象倉庫名稱的數組(用到一個或多個對象倉庫)。第二個參數,指定事務中能夠進行的操做類型,包括readonly
(只讀)和readwrite
(可讀寫)。
2.獲取IDBObjectStore對象
var objectStore = transaction.objectStore(storeName);
事務建立結束後,獲取須要進行操做的對象倉庫——IDBObjectStore對象,在這個對象下,進行具體的增刪改查操做。
對於多事務處理的狀況,事務在被建立的時候就已經開始了,所以對對象倉庫的操做順序,由事務建立的順序決定。樣例參見https://developer.mozilla.org/zh-CN/docs/Web/API/IDBTransaction
function add(){ //建立事務 var transaction = db.transcation('person', 'readwrite'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //執行新增操做並返回IDBRequest對象,若是主鍵設置爲自增,參數可不包含主鍵 var request = objectStore.add({'name': '張三', 'phone': '13910002000'}); request.onsuccess = function(event){ //插入成功 //返回新增數據的主鍵 console.log(request.result); } request.onerror = function(event){ //插入失敗,失敗的緣由能夠在event中查看 } } add();
function remove(){ //建立事務 var transaction = db.transcation('person', 'readwrite'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //執行刪除操做並返回IDBRequest對象,函數的參數爲要刪除數據的主鍵,數據類型爲Number,或爲IDBKeyRange var request = objectStore.delete(1); request.onsuccess = function(event){ //刪除成功 } request.onerror = function(event){ //刪除失敗 } } remove();
查詢能夠依據主鍵值進行,或者經過建立的索引進行。
1.經過主鍵進行查詢
function get(){ //建立事務 var transaction = db.transcation('person', 'readonly'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //執行查詢操做並返回IDBRequest對象,函數的參數爲要查詢數據的主鍵,數據類型爲Number,或爲IDBKeyRange var request = objectStore.get(1); request.onsuccess = function(event){ //查詢成功 //返回查詢結果,知足查詢條件的數據對象 console.log(request.result); } request.onerror = function(event){ //查詢失敗 } } get();
2.經過索引進行查詢
經過建立的索引進行查詢時,須要首先經過IDBObjectStore對象打開某個名稱的索引,獲取IDBIndex對象,再經過IDBIndex對象進行查詢操做。
function get(){ //建立事務 var transaction = db.transcation('person', 'readonly'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //獲取IDBIndex對象,參數爲索引的名稱 var index = objectStore.index('phone_index'); //執行查詢操做並返回IDBRequest對象,函數的參數爲要查詢數據,或爲IDBKeyRange var request = index.get('13910002000'); request.onsuccess = function(event){ //查詢成功 //返回查詢結果,知足查詢條件的數據對象 console.log(request.result); } request.onerror = function(event){ //查詢失敗 } } get();
查詢多條數據時,有兩種方式,第一種是經過IDBObjectStore對象或者IDBIndex對象的getAll
方法(indexedDB 2.0),第二種是經過遊標進行數據遍歷。
1.使用getAll方法
function getAll(){ //建立事務 var transaction = db.transcation('person', 'readonly'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //獲取IDBIndex對象,參數爲索引的名稱 var index = objectStore.index('age_index'); //執行查詢操做並返回IDBRequest對象 //函數的第一個參數爲要查詢數據,或爲IDBKeyRange,可選,若是不傳則返回全部數據 //函數的第二個參數爲要返回的數據數量,可選,若是不加則返回全部知足條件的數據,數據類型爲Number,整數 var request = index.getAll('20', 10); request.onsuccess = function(event){ //查詢成功 //返回查詢結果,知足查詢條件的數據對象數組 console.log(request.result); } request.onerror = function(event){ //查詢失敗 } } getAll();
2.使用遊標遍歷數據
function readAll(){ //建立事務 var transaction = db.transcation('person', 'readonly'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //openCursor函數擁有兩個可選參數,第一個參數爲查詢條件 //第二個參數爲查詢方向,默認爲next,還能夠設置爲prev //項目基本沒有使用這種方式遍歷數據,因此具體參數設置/是否可使用索引等還沒有驗證,等之後補充 objectStore.openCursor().onsuccess = function(event){ var cursor = event.target.result; if (cursor) { console.log('Id: ' + cursor.key); console.log('Name: ' + cursor.value.name); console.log('Age: ' + cursor.value.phone); cursor.continue(); } else { console.log('沒有更多數據了!'); } } } readAll();
function update(){ //建立事務 var transaction = db.transcation('person', 'readwrite'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //執行更新操做並返回IDBRequest對象,函數的參數要更新的數據,包含主鍵 var request = objectStore.put({'id': 1, 'name': '張三', 'phone': '13910002000'}); request.onsuccess = function(event){ //更新成功 } request.onerror = function(event){ //更新失敗 } } update();
經過以上部分的介紹,咱們能夠掌握indexedDB的基本操做,完成一些簡單的、基本的業務需求。然而,咱們的業務需求每每不會這麼簡單,這個部分,根據我在項目中遇到的一些問題,總結一下較爲複雜的業務邏輯處理方式。若是各位大拿有更好的處理方式,也但願不吝賜教。
在上邊寫的例子中,爲了簡單的介紹indexedDB操做的各個方法,並無對其進行嚴謹的封裝。在實際生產應用中,增刪改查的操做都應該作好封裝,方便複用。簡單一點的封裝能夠把數據庫對象、要查詢條件對象倉庫名稱、查詢條件、查詢成功回調和查詢失敗回調等設置爲函數的參數。好一點的方法,能夠考慮使用Promise進行相關操做的封裝。
全部的數據庫操做,都須要等待數據庫打開成功的回調,拿到IDBDatabase對象的實例以後,纔可以進行操做。對於須要用戶去操做的狀況,用戶須要填寫數據,或者填寫查詢數據,等這些耗時的操做完成時,打開數據庫操做通常已經完成了,所以不用作什麼特別處理也不會出現問題。
可是在頁面初始化就須要拿數據的狀況,因爲打開數據庫的操做是異步的,頗有可能出現數據庫打開還未成功,請求數據庫數據的函數就已經開始執行了,致使數據獲取失敗。所以,比較穩妥的方式,應該是當打開數據庫這個異步操做成功執行成功回調時,使用EventEmitter等庫進行一個事件觸發,讓監聽這個事件的函數知曉indexedDB已經準備完畢,能夠進行相關操做。
在關係型數據庫中,能夠經過SQL語句完成連表查詢,而在indexedDB中,因爲沒有SQL,沒法進行連表查詢的操做。若是想要查詢到更多的數據,目前我只能經過數據冗餘去完成。這就要求在設計數據庫的時候,提早把相關的功能邏輯考慮到。
在基礎操做介紹中,對於對象倉庫的增刪改查中,尤爲是在查詢操做,函數的參數出了能夠爲主鍵值、具體的查詢值等,還能夠爲一個IDBKeyRange對象實例。經過使用IDBKeyRange,咱們能夠把查詢條件約束到一個範圍中。IDBKeyRange提供的方法和對應的查詢值約束範圍包括:
IDBKeyRange.upperBound(x) keys <= x IDBKeyRange.upperBound(x, ture) keys < x IDBKeyRange.lowerBound(y) keys >= x IDBKeyRange.lowerBound(y, ture) keys > x IDBKeyRange.bound(x, y) keys >= x && keys <= y IDBKeyRange.bound(x, y, true, ture) keys > x && keys < y IDBKeyRange.bound(x, y, true, false) keys > x && keys <= y IDBKeyRange.bound(x, y, false, true) keys >= x && keys < y IDBKeyRange.only(z) keys = z
使用方法爲:
function getAll(){ //建立事務 var transaction = db.transcation('person', 'readonly'); //獲取IDBObjectStore對象 var objectStore = transaction.objectStore('person'); //獲取IDBIndex對象,參數爲索引的名稱 var index = objectStore.index('age_index'); //獲取年齡大於等於10歲小於等於20歲的數據 var request = index.getAll(IDBKeyRange.bound(10, 20)); request.onsuccess = function(event){ //查詢成功 //返回查詢結果,知足查詢條件的數據對象數組 console.log(request.result); } request.onerror = function(event){ //查詢失敗 } } getAll();
在關係型數據庫中,能夠經過如下SQL語句完成多條件查詢:select * from person where age = 20 AND name = '張三'
在indexedDB中,沒有提供多條件查詢的直接方法,想要實現多條件查詢,目前一個可行的方法爲,建立多屬性索引,並藉助IDBKeyRange進行查詢。
//建立數據倉庫時 request.onupgradeneeded = function(event){ var _db = event.target.result; var objectStore; if(!_db.objectStoreNames.contains('person')){ objectStore = _db.createObjectStore('person', { keyPath: 'id', autoIncrement: true }); //建立一個多屬性索引 objectStore.createIndex('multi_index', ['name', 'age'], {unique: false}); } } //進行查詢時 function getAll(){ var transaction = db.transcation('person', 'readonly'); var objectStore = transaction.objectStore('person'); var index = objectStore.index('multi_index'); var request = index.getAll(IDBKeyRange.only(['張三', 20])); request.onsuccess = function(event){ console.log(request.result); } request.onerror = function(event){ } } getAll();
一旦清楚了瀏覽器緩存,一切數據都將會被清除。因此,要麼核心業務不要過分依賴於indexedDB,要麼本身整一套方法機制進行數據的按期向服務器同步,要麼。。。你跟用戶說一下,不要讓他沒事清緩存。。。T-T
另外,在隱私模式下,推出瀏覽器以後,數據也會被清理(沒有驗證過)。
做爲一個以關係型數據庫開始學習的人,剛上手去作indexedDB的時候,因爲是第一次接觸NOSQL,不怎麼適應,不少邏輯都已經被關係型數據庫和SQL語句給固化了,結果在數據庫設計和使用上走了不少彎路,特別藍瘦。好在我遇到的坑,都已經跳出去了。。。關於indexedDB,個人總結目前就這些了,還想要進一步學習的同窗,本身去查閱文檔吧。有存在問題的地方,還望各位大拿指正。