在開始介紹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的,從目前浏覽器的相容性來看,也表明了這種結果:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLuVjMiNXOXl1bk1mYox2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0YzN3MjMxgTMwMzNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
可以看到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對比内容:
不能再繼續說下去了,估計看完上面對比表中的内容,已經有很多同學已經有些不知是以然了,因為其中涉及到了很多資料庫相關的概念,要想輕松了解上面内容以及更輕松掌握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資料庫緩存資料,兩者一結合而就可以實作百分百的離線開發,聽上去還是很厲害的具體的實踐還是需要各自多動手驗證了.