天天看點

HTML5 IndexedDB本地存儲介紹

在開始介紹IndexedDB之前我先簡單說一下Web SQL Database

一、indexedDB為何替代了Web SQL Database?

跟小朋友的教育從來沒有什麼“赢在起跑線”這種說法一樣,在前端領域,也不是哪來先出來哪個就在日後引領風騷的。

HTML5 indexedDB和Web SQL Database都是本地資料庫資料存儲,Web SQL Database資料庫要出來的更早,然并卵。從2010年11月18日W3C宣布舍棄Web SQL database草案開始,就已經注定Web SQL Database資料庫是明日黃花。

未來一定是indexedDB的,從目前浏覽器的相容性來看,也表明了這種結果:

HTML5 IndexedDB本地存儲介紹
HTML5 IndexedDB本地存儲介紹

可以看到IE和Firefox并不支援Web SQL Database,基本上可以斷定永遠也不會支援,規範都不認可,實在沒有浪費精力去支援的理由。

好,現在我們知道了indexedDB取代Web SQL Database大局已定,那大家知道為何Web SQL Database會被舍棄嗎?

下面是一段Web SQL Database代碼:

database.transaction(function(tx) {
    tx.executeSql("CREATE TABLE IF NOT EXISTS tasks (id REAL UNIQUE, text TEXT)", []);
}
           

可以看到直接把SQL語句弄到JS中了,主流的關系型資料庫即視感,這麼設計看上去似乎無可厚非,但恰恰這個設計成為了Web SQL Database被舍棄的重要原因之一:一是學習成本高了很多,SQL雖然本身并不複雜,但跨度較大,例如我司玩JS的工程師和玩SQL的工程師中間還隔了個玩php的工程師;二是本身使用很不友善,需要把JS對象轉換成關系型的字元串語句,很啰嗦的。

而indexedDB直接JS對象入庫,無縫對接。

下表為更詳細的indexedDB和Web SQL Database對比内容:

HTML5 IndexedDB本地存儲介紹

不能再繼續說下去了,估計看完上面對比表中的内容,已經有很多同學已經有些不知是以然了,因為其中涉及到了很多資料庫相關的概念,要想輕松了解上面内容以及更輕松掌握indexedDB的相關知識,這些概念還是需要掌握的。

二、務必先了解資料庫的一些概念

對于前端同學,資料庫概念沒必要了解十分精準,隻需要知道大概怎麼回事就好了,是以,下面的一些解釋會其意,勿嚼字。

1. 關系型資料庫和非關系型資料庫

“關系型資料庫”是曆史悠久,已經有半個世紀,占據主流江山的資料庫模型,估計諸位公司的網站多半都是使用的這種資料庫模型。而“非關系型資料庫”(NoSQL資料庫)則要年輕很多,根據資料顯示是Carlo Strozzi在1998年提出來的,20年不到,使用鍵值對存儲資料,且結構不固定,非常類似JavaScript中的純對象。

var obj = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3',
  ...
};
           

“關系型資料庫”對一緻性要求非常嚴格,例如要寫入100個資料,前99個成功了,結果第100個不合法,此時事務會復原到最初狀态。這樣保證事務結束和開始時候的存儲資料處于一緻狀态。非常适合銀行這種對資料一緻性要求非常高的場景。但是,這種一緻性保證是犧牲了一部分性能的。

但是,對于微網誌,QQ空間這類web2.0應用,一緻性卻不是顯得那麼重要,比方說朋友A看我的首頁和朋友B看我的首頁資訊有不一樣,沒什麼大不了的,有個幾秒差異很OK的。雖然這類應用對一緻性要求不高,但對性能要求卻很高,因為讀寫實在太頻繁了。如果使用“關系型資料庫”,一緻性的好處沒怎麼收益,反而性能問題比較明顯,此時,不保證一緻性但性能更優的“非關系型資料庫”則更合适。同時,由于“非關系型資料庫”的資料結構不固定,非常容易擴充。由于對于社交網站,需求變動那是一日三餐常有的事,添加新字段在所難免,“非關系型資料庫”輕松上陣,如果使用“關系型資料庫”,多半要資料大變動,要好好琢磨琢磨了。

從氣質上講,“關系型資料庫”穩重持久,“非關系型資料庫”迅速靈動。在前端領域,Web SQL Database是“關系型資料庫”,indexedDB是“非關系型資料庫”。

2. 了解資料庫中的事務-transaction

  資料庫的事務(英文為’transaction’),我們可以了解為對資料庫的操作,而且專指一個序列上的操作。舉個例子,銀行轉賬,一個賬号錢少了然後另一個賬号錢多了,這兩個操作要麼都執行,要麼都不執行。像這種操作就可以看成一個事務。事務的提出主要是為了保證并發情況下保持資料一緻性。關系型資料庫中的事務具有下面4個基本特征:

  • 原子性(Atomicity):事務中的所有操作作為一個整體送出或復原。
  • 一緻性(Consistemcy):事物完成時,資料必須是一緻的,以保證資料的無損。
  • 隔離性(Isolation):對資料進行修改的多個事務是彼此隔離的。
  • 持久性(Durability):事務完成之後,它對于系統的影響是永久的,該修改即使出現系統故障也将一直保留。

通常專業描述中會抽取這4個基本特性的首字母,統稱為“ACID特性”。事務執行過程可以粗淺地了解為:開始事務,巴拉巴拉操作,如果錯誤,復原(rollback),如果沒問題,送出(commit),結束事務。

3. 資料庫中的“鎖”-lock

  資料庫中的“鎖”是保證資料庫資料高并發時候資料一緻性的一種機制。舉個例子:現有兩處火車票售票點,同時讀取某發現上海到北京車票餘額為

5

。此時兩處售票點同時賣出一張車票,同時修改餘額為

5-1

也就是

4

寫回資料庫,這樣就造成了實際賣出兩張火車票而資料庫中的記錄卻隻少了

1

張。為了避免發生這種狀況,就有了鎖機制,也就是執行多線程時用于強行限制資源通路。

三、從簡單實用的執行個體開始學習indexedDB

要想系統學習indexedDB相關知識,可以去MDN啃API,假以時日就可以成為前端indexedDB方面的專家。但是這種學習方法周期長,過于痛苦,無法立竿見影,因為API實在太多,天天花個把小時,估計也需要數周時間才能全部通透。

一想到投入産出比這麼低,于是我決定從執行個體入手開始學習,能夠實作基本的資料庫增删改查功能就好,基本上80%+的相關需求都能從容搞定;等日後有機會再慢慢深入。

1. 首先打開indexedDB資料庫

indexedDB資料庫打開非常簡單,文法就是字面意思:

window.indexedDB.open(dbName, version);
           

dbName

就是資料庫名稱,例如demo中使用的是

'project'

version

表示資料庫的版本,根據我的了解,當我們對資料庫的字段進行增加或修改時候,需要增加個版本。

通常,打開indexedDB資料庫是和一些回調方法一起出現的,代碼套路比較固定,基本上如果大家在實際項目中使用的話,都可以使用類似的代碼:

// 資料庫資料結果
var db;
// 打開資料庫
var DBOpenRequest = window.indexedDB.open('project', 1);
// 資料庫打開成功後
DBOpenRequest.onsuccess = function(event) {        
    // 存儲資料結果
    db = DBOpenRequest.result;
    // 做其他事情...
};

// 下面事情執行于:資料庫首次建立版本,或者window.indexedDB.open傳遞的新版本(版本數值要比現在的高)
DBOpenRequest.onupgradeneeded = function(event) {
    // 通常對主鍵,字段等進行重定義,具體參見demo
};
           

其中,最重要就是紅色高亮的

db

變量,我們後面所有的資料庫操作都離不開它。

2. 建立indexedDB資料庫的主鍵和字段

在我們開始資料庫的增加删除操作之前,首先要把我們資料庫的主鍵和一些字段先建好。是在

onupgradeneeded

這個回調方法中設定的,這個回調方法執行于資料庫首次建立版本或者資料庫indexedDB方法中傳遞的版本号比目前版本号要高。

我們對資料庫某一行資料進行增加删除操作,我們是沒有必要對資料庫的版本号進行修改的。但是對于字段修改就不一樣了,比方說原來是

5

列資料,我們現在改成

6

列,由于相關設定是在

onupgradeneeded

回調中,是以,我們需要增加版本号來觸發字段修改。demo頁面這部分代碼如下

DBOpenRequest.onupgradeneeded = function(event) {
    var db = event.target.result;

    // 建立一個資料庫存儲對象
    var objectStore = db.createObjectStore(dbName, { 
        keyPath: 'id',
        autoIncrement: true
    });

    // 定義存儲對象的資料項
    objectStore.createIndex('id', 'id', {
        unique: true    
    });
    objectStore.createIndex('name', 'name');
    objectStore.createIndex('begin', 'begin');
    objectStore.createIndex('end', 'end');
    objectStore.createIndex('person', 'person');
    objectStore.createIndex('remark', 'remark');
};
           

這裡出現了一個比較重要的概念,叫做objectStore,這是indexedDB可以替代Web SQL Database的重要優勢所在,我稱之為“存儲對象”。

objectStore.add()可以向資料庫添加資料,objectStore.delte()可以删除資料,objectStore.clear()可以清空資料庫,objectStoreput()可以替換資料等還有很多很多操作API。

在這裡,我們使用

objectStore

來建立資料庫的主鍵和普通字段。

db.createObjectStore

在建立主鍵同時傳回“存儲對象”,本示範中使用自動遞增的

id

字段作為關鍵路徑。

createIndex()

方法可以用來建立索引,可以了解為建立字段,文法為:

objectStore.createIndex(indexName, keyPath, objectParameters)
           

其中:

indexName:建立的索引名稱,可以使用空名稱作為索引。

keyPath:索引使用的關鍵路徑,可以使用空的

keyPath

, 或者

keyPath

傳為數組

keyPath

也是可以的。

objectParameters:可選參數。常用參數之一是

unique

,表示該字段值是否唯一,不能重複。例如,本demo中

id

是不能重複的,于是有設定:

objectStore.createIndex('id', 'id', {
  unique: true    
});
           

3. indexedDB資料庫添加資料

字段建好了之後,我們就可以給indexedDB資料庫添加資料了。由于資料庫的操作都是基于事務(transaction)來進行,于是,無論是添加編輯還是删除資料庫,我們都要先建立一個事務(transaction),然後才能繼續下面的操作。文法就是名稱:

var transaction = db.transaction(dbName, "readwrite");
           

 dbName就是資料庫的名稱,于是我們的資料庫添加資料操作變得很簡單:

// 建立一個事務
var transaction = db.transaction('project', "readwrite");
// 打開存儲對象
var objectStore = transaction.objectStore('project');
// 添加到資料對象中
objectStore.add(newItem);
           

使用一行語句表示就是:

db.transaction('project', "readwrite").objectStore('project').add(newItem);
           

這裡newItem直接就是一個原生的純粹的JavaScript對象,在本demo中newItem資料類似下面這樣:

{
  "name": "第一個項目",
  "begin": "2019-07-16",
  "end": "2057-07-16",
  "person": "Yang Hao Long",
  "remark": "測試測試"
}
           

可以發現,當我們使用indexedDB資料庫添加資料的時候,根本就不用去額外學習SQL語句,原始的JavaScript對象直接無縫對接.

4. indexedDB資料庫的編輯

原理為,先根據

id

獲得對應行的存儲對象,方法objectStore.get(id),然後在原存儲對象上進行替換,再使用objectStore.put(record)進行資料庫資料替換。

代碼示意:

function edit(id, data) {
    // 建立事務
    var transaction = db.transaction('project', "readwrite");
    // 打開已經存儲的資料對象
    var objectStore = transaction.objectStore(project);
    // 擷取存儲的對應鍵的存儲對象
    var objectStoreRequest = objectStore.get(id);
    // 擷取成功後替換目前資料
    objectStoreRequest.onsuccess = function(event) {
        // 目前資料
        var myRecord = objectStoreRequest.result;
        // 周遊替換
        for (var key in data) {
            if (typeof myRecord[key] != 'undefined') {
                myRecord[key] = data[key];
            }
        }
        // 更新資料庫存儲資料             
        objectStore.put(myRecord);
    };
}
           

其中,id就是要替換的資料庫行的

id

字段值,因為唯一的主鍵,可以保證準确和高性能;data則是需要替換的資料對象,例如從“測試測試”修改為“我是大俠~”,則調用:

edit(1, {
  remark: '我是大俠~'
});
           

就可以了,跟我們平常寫JS代碼就沒有什麼差別。

5. indexedDB資料庫的删除

和添加操作正好相反,但代碼結構卻是類似的,一行表示法:

db.transaction('project', "readwrite").objectStore('project').delete(id);
           

id

表示需要删除行

id

字段對應的值。

6. indexedDB資料庫的擷取

indexedDB資料庫的擷取使用Cursor APIs和Key Range APIs 。

也就是使用“遊标API”和“範圍API”。在本文的示範頁面中,隻使用了“遊标API”,直接顯示全部資料,對于“範圍API”并沒有使用,但這裡也會簡單介紹下。

“遊标API”可以讓我們一行一行讀取資料庫資料,代碼示意:

var objectStore = db.transaction(dbName).objectStore(dbName);
objectStore.openCursor().onsuccess = function(event) {
    var cursor = event.target.result;
    if (cursor) {
        // cursor.value就是資料對象
        // 遊标沒有周遊完,繼續
        cursor.continue();
    } else {
        // 如果全部周遊完畢...
    }
}
           

四、indexedDB存儲和localStorage存儲對比

  • indexedDB存儲IE10+支援,localStorage存儲IE8+支援,後者相容性更好;
  • indexedDB存儲比較适合鍵值對較多的資料,我之前不少項目需要存儲多個字段,使用的是localStorage存儲,結果每次寫入和寫出都要字元串化和對象化,很麻煩,如果使用indexedDB會輕松很多,因為無需資料轉換。
  • indexedDB存儲可以在workers中使用,localStorage貌似不可以。這就使得在進行PWA開發的時候,資料存儲的技術選型落在了indexedDB存儲上面。

總結下就是,如果是浏覽器主窗體線程開發,同時存儲資料結構簡單,例如,就存個true或false,顯然localStorage上上選;如果資料結構比較複雜,同時對浏覽器相容性沒什麼要求,可以考慮使用indexedDB;如果是在Service Workers中開發應用,隻能使用indexedDB資料存儲。

五、結束語

indexedDB資料庫的使用目前可以直接在http協定下使用,這個和cacheStorage 緩存存儲必須使用https協定不一樣。是以就應用場景來講,indexedDB資料庫還是挺廣的,考慮到IE10也支援,是以基本可以确定在實際項目中應用是絕對不成問題的。例如,頁面中一些不常變動的結構化資料,我們就可以使用indexedDB資料庫存儲在本地,有助于增強頁面的互動性能。cacheStorage緩存頁面,indexedDB資料庫緩存資料,兩者一結合而就可以實作百分百的離線開發,聽上去還是很厲害的具體的實踐還是需要各自多動手驗證了.

繼續閱讀