瀏覽器數據庫 indexedDB

1.關於indexedDB

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瀏覽器

2.基本使用

1.建立/打開數據庫

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

2.建立對象倉庫及索引

indexedDB中,使用對象倉庫來存儲具體的數據,其功能相似與關係型數據庫的表。
不一樣於關係型數據庫的表結構,對象倉庫中數據的存儲結構以下圖所示:
1576657854506.jpg
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,若是沒有索引,咱們只能根據主鍵去查詢數據。只有創建索引以後,才能將索引對應的屬性做爲查詢條件進行數據查詢。

進行數據庫版本升級的狀況下,以前版本的對象倉庫是依然存在的,無需重複建立。若是須要更新對象倉庫屬性,須要先刪除原有對象倉庫,再從新建立。函數

3 建立事務與獲取IDBObjectStore對象

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

4 向對象倉庫中新增數據
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();
5 刪除對象倉庫中的一條數據
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();
6 查詢對象倉庫中的一條數據

查詢能夠依據主鍵值進行,或者經過建立的索引進行。

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();
7 查詢對象倉庫中的多條數據

查詢多條數據時,有兩種方式,第一種是經過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();
8 更新對象倉庫中的一條數據
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();

3.複雜邏輯或問題的解決

經過以上部分的介紹,咱們能夠掌握indexedDB的基本操做,完成一些簡單的、基本的業務需求。然而,咱們的業務需求每每不會這麼簡單,這個部分,根據我在項目中遇到的一些問題,總結一下較爲複雜的業務邏輯處理方式。若是各位大拿有更好的處理方式,也但願不吝賜教。

1 關於函數的封裝

在上邊寫的例子中,爲了簡單的介紹indexedDB操做的各個方法,並無對其進行嚴謹的封裝。在實際生產應用中,增刪改查的操做都應該作好封裝,方便複用。簡單一點的封裝能夠把數據庫對象、要查詢條件對象倉庫名稱、查詢條件、查詢成功回調和查詢失敗回調等設置爲函數的參數。好一點的方法,能夠考慮使用Promise進行相關操做的封裝。

2 關於打開數據庫

全部的數據庫操做,都須要等待數據庫打開成功的回調,拿到IDBDatabase對象的實例以後,纔可以進行操做。對於須要用戶去操做的狀況,用戶須要填寫數據,或者填寫查詢數據,等這些耗時的操做完成時,打開數據庫操做通常已經完成了,所以不用作什麼特別處理也不會出現問題。
可是在頁面初始化就須要拿數據的狀況,因爲打開數據庫的操做是異步的,頗有可能出現數據庫打開還未成功,請求數據庫數據的函數就已經開始執行了,致使數據獲取失敗。所以,比較穩妥的方式,應該是當打開數據庫這個異步操做成功執行成功回調時,使用EventEmitter等庫進行一個事件觸發,讓監聽這個事件的函數知曉indexedDB已經準備完畢,能夠進行相關操做。

3 關於連表查詢的問題

在關係型數據庫中,能夠經過SQL語句完成連表查詢,而在indexedDB中,因爲沒有SQL,沒法進行連表查詢的操做。若是想要查詢到更多的數據,目前我只能經過數據冗餘去完成。這就要求在設計數據庫的時候,提早把相關的功能邏輯考慮到。

4 關於IDBKeyRange

在基礎操做介紹中,對於對象倉庫的增刪改查中,尤爲是在查詢操做,函數的參數出了能夠爲主鍵值、具體的查詢值等,還能夠爲一個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();
5 關於多條件查詢

在關係型數據庫中,能夠經過如下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();
6 關於數據庫清除

一旦清楚了瀏覽器緩存,一切數據都將會被清除。因此,要麼核心業務不要過分依賴於indexedDB,要麼本身整一套方法機制進行數據的按期向服務器同步,要麼。。。你跟用戶說一下,不要讓他沒事清緩存。。。T-T
另外,在隱私模式下,推出瀏覽器以後,數據也會被清理(沒有驗證過)。

4. 結語

做爲一個以關係型數據庫開始學習的人,剛上手去作indexedDB的時候,因爲是第一次接觸NOSQL,不怎麼適應,不少邏輯都已經被關係型數據庫和SQL語句給固化了,結果在數據庫設計和使用上走了不少彎路,特別藍瘦。好在我遇到的坑,都已經跳出去了。。。關於indexedDB,個人總結目前就這些了,還想要進一步學習的同窗,本身去查閱文檔吧。有存在問題的地方,還望各位大拿指正。

相關文章
相關標籤/搜索