新一代的前端存儲方案--indexedDB

前端存儲

  咱們都知道在前端開發當中,有時會由於某些需求,要將一些數據存儲在前端本地當中.好比說:爲了優化性能,將一些經常使用的數據存在本地,這樣之後須要的時候直接從本地拿,不須要再向後端進行請求.還有就是爲了防止CSRF攻擊,後端給前端一個token,前端就須要將這個token存在本地.以後每次請求都須要帶上這個token.等等不一而足.前端

  而這些需求就不油避免的造就一個前端的發展方向--前端存儲web

  在前端的'上古時代'裏,咱們前端想要存儲數據,只有一種方式,那就是Cookie.可是Cookie雖然能夠作前端存儲方案,可是卻也有着不少侷限性.首先它的存儲空間大小隻有4K,其次它的存儲有效時間有限制,而後存在Cookie中的數據,在你每次進行請求的時候都會將它帶上.使得每次的請求數據都會無心義的增大.最後,也是最重要的一點.Cookie設計之初就不是就是讓咱們前端存數據用的.它只是爲了讓網站驗證用戶身份用的.至於Cookie的本地存儲功能只是它的一個手段而已.關於這點大家能夠看下個人另一篇文章---在HTML5的時代,從新認識Cookie數據庫

  綜上所述,使用Cookie做爲前端存儲有這許多缺點,因此通過前端社區的不斷努力,在HTML5中有了真正的前端存儲方案Web Storage.它分爲兩種,一種是永久存儲的localStorage,一種是會話期間存儲的sessionStorage.對比Cookie,Web Storage的優點很明顯:後端

  1. 存儲空間更大,有5M大小
  2. 在瀏覽器發送請求是不會帶上web Storage裏的數據
  3. 更加友好的API
  4. 能夠作永久存儲(localStorage).

  這一切看起來很完美,可是隨着前端的不斷髮展,web Storage也有了一些不太合適的地方:數組

  1. 隨着web應用程序的不斷髮展,5M的存儲大小對於一些大型的web應用程序來講有些不夠
  2. web Storage只能存儲string類型的數據.對於Object類型的數據只能先用JSON.stringify()轉換一下在存儲.

  基於上述緣由,前端社區又提出了瀏覽器數據庫存儲這個概念.而Web SQL DatabaseindexedDB(索引數據庫)是對這個概念的實現.其中Web SQL Database在目前來講基本已經被放棄.因此目前主流的瀏覽器數據庫的實現就是indexedDB(索引數據庫).也就是咱們要介紹的 新一代的前端存儲方案--indexedDB瀏覽器

什麼是indexedDB

indexedDB的介紹

IndexedDB 是一種使用瀏覽器存儲大量數據的方法.它創造的數據能夠被查詢,而且能夠離線使用. IndexedDB對於那些須要存儲大量數據,或者是須要離線使用的程序是很是有效的解決方法. --- MDNsession

indexedDB的概念

  使用IndexedDB,你能夠存儲或者獲取數據,使用一個key索引的。 你能夠在事務(transaction)中完成對數據的修改。和大多數web存儲解決方案相同,indexedDB也聽從同源協議(same-origin policy). 因此你只能訪問同域中存儲的數據,而不能訪問其餘域的。
  API包含異步(asynchronous) API 和同步(synchronous)API兩種。 異步API適合大多數狀況, 同步API必須同 WebWorkers一同使用. 目前,沒有主流瀏覽器支持同步API。 即便同步API被支持了,你也會在大多數的狀況使用異步API。
  IndexedDB 是 WebSQL 數據庫的取代品, W3C組織在2010年11月18日廢棄了webSql. IndexedDB 和WebSQL的不一樣點在於WebSQL 是關係型數據庫(複雜)IndexedDB 是key-value型數據庫(簡單好使).併發

  上面是MDN上對於IndexedDB的介紹.其簡單而言,indexedDB就是一個基於事務操做的key-value型數前端數據庫.其API大可能是異步的異步

indexedDB的使用

建立一個indexedDB數據庫

const request = indexedDB.open('myDatabase', 1);

request.addEventListener('success', e => {
   console.log("鏈接數據庫成功");
});

request.addEventListener('error', e => {
    console.log("鏈接數據庫失敗");
});
複製代碼

  在上面代碼中咱們使用indexedDB.open()建立一個indexedDB數據庫.open()方法接受能夠接受兩個參數.第一個是數據庫名,第二個是數據庫的版本號.同時返回一個IDBOpenDBRequest對象用於操做數據庫.其中對於open()的第一個參數數據庫名,open()會先去查找本地是否已有這個數據庫,若是有則直接將這個數據庫返回,若是沒有,則先建立這個數據庫,再返回.對於第二個參數版本號,則是一個可選參數,若是不傳,默認爲1.但若是傳入就必須是一個整數.async

  在經過對indexedDB.open()方法拿到一個數據庫對象IDBOpenDBRequest咱們能夠經過監聽這個對象的success事件和error事件來執行相應的操做.

建立一個對象倉庫

  再有了一個數據庫以後,咱們獲取就想要去存儲數據了,可是單隻有數據庫還不夠,咱們還須要有對象倉庫(object store).對象倉庫(object store)是indexedDB數據庫的基礎,其相似於MySQL中表的概念.

  要建立一個對象倉庫必須在upgradeneeded事件中,而upgradeneeded事件只會在版本號更新的時候觸發.這是由於indexedDB API中不容許數據庫中的數據倉庫在同一版本中發生變化

const request = indexedDB.open('myDatabase', 2);

request.addEventListener('upgradeneeded', e => {
    const db = e.target.result;
    const  store = db.createObjectStore('Users', {keyPath: 'userId', autoIncrement: false});
    console.log('建立對象倉庫成功');
});
複製代碼

  在上述代碼中咱們監聽upgradeneeded事件,並在這個事件觸發時使用createObjectStore()方法建立了一個對象倉庫.createObjectStore()方法接受兩個參數,第一個是對象倉庫的名字,在同一數據庫中,倉庫名不能重複.第二個是可選參數.用於指定數據的主鍵,以及是否自增主鍵.

建立事務

  OK如今咱們有了數據庫和對象倉庫了,咱們是否就能夠存儲數據了了.很抱歉,仍是不行.咱們還差最後同樣東西----事務.

什麼是事務

  一個數據庫事務一般包含了一個序列的對數據庫的讀/寫操做。它的存在包含有如下兩個目的

  1. 爲數據庫操做序列提供了一個從失敗中恢復到正常狀態的方法,同時提供了數據庫即便在異常狀態下仍能保持一致性的方法。
  2. 當多個應用程序在併發訪問數據庫時,能夠在這些應用程序之間提供一個隔離方法,以防止彼此的操做互相干擾。

  並不是任意的對數據庫的操做序列都是數據庫事務。數據庫事務擁有如下四個特性,習慣上被稱之爲ACID特性。

  • 原子性(Atomicity):事務做爲一個總體被執行,包含在其中的對數據庫的操做要麼所有被執行,要麼都不執行
  • 一致性(Consistency):事務應確保數據庫的狀態從一個一致狀態轉變爲另外一個一致狀態。一致狀態的含義是數據庫中的數據應知足完整性約束
  • 隔離性(Isolation):多個事務併發執行時,一個事務的執行不該影響其餘事務的執行
  • 持久性(Durability):已被提交的事務對數據庫的修改應該永久保存在數據庫中

  上面是維基百科上對數據庫事務的解釋.簡單來講事務就是用來保證數據庫操做要麼所有成功,要麼所有失敗的一個限制.好比,在修改多條數據時,前面幾條已經成功了.,在中間的某一條是失敗了.那麼在這時,若是是基於事務的數據庫操做,那麼這時數據庫就應該重置前面數據的修改,放棄後面的數據修改.直接返回錯誤,一條數據也不修改.

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');
});
複製代碼

上述代碼中咱們使用transaction()來建立一個事務.transaction()接受兩個參數,第一個是你要操做的對象倉庫名稱,第二個是你建立的事務模式.傳入 readonly時只能對對象倉庫進行讀操做,沒法寫操做.能夠傳入readwrite進行讀寫操做.

操做數據

  好了如今有了數據庫,對象倉庫,事務以後咱們終於能夠存儲數據了.

  • add() : 增長數據。接收一個參數,爲須要保存到對象倉庫中的對象。
  • put() : 增長或修改數據。接收一個參數,爲須要保存到對象倉庫中的對象。
  • get() : 獲取數據。接收一個參數,爲須要獲取數據的主鍵值。
  • delete() : 刪除數據。接收一個參數,爲須要獲取數據的主鍵值。

add 和 put 的做用相似,區別在於 put 保存數據時,若是該數據的主鍵在數據庫中已經有相同主鍵的時候,則會修改數據庫中對應主鍵的對象,而使用 add 保存數據,若是該主鍵已經存在,則保存失敗。

添加數據

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    // 保存數據
    const reqAdd = store.add({'userId': 1, 'userName': '李白', 'age': 24});

    reqAdd.addEventListener('success', e => {
      console.log('保存成功')
    })
});
複製代碼

獲取數據

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    // 獲取數據
    const reqGet = store.get(1);

    reqGet.addEventListener('success', e => {
      console.log(this.result.userName);    // 李白
    })
});
複製代碼

刪除數據

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    // 刪除數據
    const reqDelete = store.delete(1);

    reqDelete.addEventListener('success', e => {
      console.log('刪除數據成功');    // 李白
    })
});
複製代碼

使用遊標

  在上面當中咱們使用get()方法傳入一個主鍵來獲取數據,可是這樣只可以獲取到一條數據.若是咱們想要獲取多條數據了怎麼辦.咱們可使用遊標,來獲取一個區間內的數據.

  要使用遊標,咱們須要使用對象倉庫上的openCursor()方法建立幣打開.openCursor()方法接受兩個參數.

openCursor(range?: IDBKeyRange | number | string | Date | IDBArrayKey, direction?: IDBCursorDirection): IDBRequest;
複製代碼

  第一個是範圍,範圍能夠是一個IDBKeyRange對象.用如下方式建立.

// boundRange 表示主鍵值從1到10(包含1和10)的集合。
// 若是第三個參數爲true,則表示不包含最小鍵值1,若是第四參數爲true,則表示不包含最大鍵值10,默認都爲false
var boundRange = IDBKeyRange.bound(1, 10, false, false);

// onlyRange 表示由一個主鍵值的集合。only() 參數則爲主鍵值,整數類型。
var onlyRange = IDBKeyRange.only(1);

// lowerRaneg 表示大於等於1的主鍵值的集合。
// 第二個參數可選,爲true則表示不包含最小主鍵1,false則包含,默認爲false
var lowerRange = IDBKeyRange.lowerBound(1, false);

// upperRange 表示小於等於10的主鍵值的集合。
// 第二個參數可選,爲true則表示不包含最大主鍵10,false則包含,默認爲false
var upperRange = IDBKeyRange.upperBound(10, false);
複製代碼

  第二個參數是方向.主要有一下幾種

  • next : 遊標中的數據按主鍵值升序排列,主鍵值相等的數據都被讀取
  • nextunique : 遊標中的數據按主鍵值升序排列,主鍵值相等只讀取第一條數據
  • prev : 遊標中的數據按主鍵值降序排列,主鍵值相等的數據都被讀取
  • prevunique : 遊標中的數據按主鍵值降序排列,主鍵值相等只讀取第一條數據

  下面讓咱們來看一個完整的例子

const request = indexedDB.open('myDatabase', 4);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    const range = IDBKeyRange.bound(1,10);

    const req = store.openCursor(range, 'next');

    req.addEventListener('success', e => {
      const cursor = this.result;
        if(cursor){
            console.log(cursor.value.userName);
            cursor.continue();
        }else{
            console.log('檢索結束');
        }
    })
});

複製代碼

  在上面的代碼中若是檢索到符合條件的數據時,咱們能夠:

  • 使用cursor.value拿到數據.
  • 使用cursor.updata()更新數據.
  • 使用cursor.delete()刪除數據.
  • 使用cursor.continue()讀取下一條數據.

索引

  在上面代碼中咱們獲取數據都是用的主鍵.可是,在不少狀況下咱們並不知道咱們須要數據的主鍵是什麼,咱們知道一個大概的條件.好比說年齡大於20歲的用戶.這個時候咱們就須要用到索引.以便有條件的查找.

建立索引

  咱們使用對象倉庫的createIndex()方法來建立一個索引.

createIndex(name: string, keyPath: string | string[], optionalParameters?: IDBIndexParameters): IDBIndex;
複製代碼

  createIndex()方法接收三個參數:

  1. 第一個參數name是索引名,不能重複.

  2. 第二個參數keyPath是你要在存儲對象上的那個屬性上創建索引,能夠是一個單個的key值,也能夠是一個包含key值集合的數組.

  3. 第三個參數optionalParameters是一個可選的對象參數{unique, multiEntry}

    • unique: 用來指定索引值是否能夠重複,爲true表明不能相同,爲false時表明能夠相同
    • multiEntry: 當第二個參數keyPath爲一個數組時.若是multiEntry是true,則會以數組中的每一個元素創建一條索引.若是是false,則以整個數組爲keyPath值,添加一條索引.

  下面讓咱們來看一個完整的例子,咱們創建一條用戶年齡的索引.

const request = indexedDB.open('myDatabase', 5);

request.addEventListener('upgradeneeded', e => {
    const db = e.target.result;
    const  store = db.createObjectStore('Users', {keyPath: 'userId', autoIncrement: false});
    const idx = store.createIndex('ageIndex','age',{unique: false})
});
複製代碼

  這樣咱們就建立了一條索引.

使用索引

  這在建立了一條索引以後咱們就能夠來使用它了.咱們使用對象倉庫上的index方法,經過傳入一個索引名.來拿到一個索引對象.

const index = store.index('ageIndex');
複製代碼

  而後咱們就可使用這個索引了.好比說咱們要拿到年齡在20歲以上的數據,升序排列.

const request = indexedDB.open('myDatabase', 4);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    const index = store.index('ageIndex');

    const req = index.openCursor(IDBKeyRange.lowerBound(20), 'next');

    req.addEventListener('success', e => {
      const cursor = e.target.result;
        if(cursor){
            console.log(cursor.value.age);
            cursor.continue();
        }else{
            console.log('檢索結束');
        }
    })
});
複製代碼

indexedDB的兼容性

上面是我對indexedDB一些粗淺的總結,但願對你們有所幫助.若是文中有何不當之處請予以斧正,謝謝.

參考資料:

個人我的網址: wangyiming.info

相關文章
相關標籤/搜索