當前位置:
首頁 > 最新 > IndexedDB 打造靠譜 Web 離線資料庫

IndexedDB 打造靠譜 Web 離線資料庫

在知乎和我在平常工作中,常常會看到一個問題:

前端現在還火嗎?

這個我只想說:

隔岸觀火的人永遠無法明白起火的原因,只有置身風暴,才能找到風眼之所在 ——『秦時明月』

你 TM 看都不看前端現在的發展,怎麼去評判前端火不火,我該不該嘗試一下其他方面的內容呢?本人為啥為這麼熱衷於新的技術呢?主要原因在於,生怕會被某一項顛覆性的內容淘汰掉,從前沿領域掉隊下來。說句人話就是: 。所以本文會從頭剖析一下 在前端裡面的應用的發展。

indexedDB 目前在前端慢慢得到普及和應用。它正朝著前端離線資料庫技術的步伐前進。以前一開始是 manifest、localStorage、cookie 再到 webSQL,現在 indexedDB 逐漸被各大瀏覽器認可。我們也可以針對它來進行技術上創新的開發。比如,現在小視頻非常流行,那麼我們可以在用戶觀看時,通過 cacheStorage 緩存,然後利用 WebRTC 技術實現 P2P 分發的控制,不過需要注意,一定要合理利用大小,不然後果真的很嚴重。

indexedDB 的整體架構,是由一系列單獨的概念串聯而成,全部概念如下列表。一眼看去會發現沒有任何邏輯,不過,這裡我順手畫了一幅邏輯圖,中間會根據 函數 的調用而相互串聯起來。

IDBRequest

IDBFactory

IDBDatabase

IDBObjectStore

IDBIndex

IDBKeyRange

IDBCursor

IDBTransaction

整體邏輯圖如下:


下文主要介紹了 indexedDB 的基本概念,以及在實際應用中的實操代碼。

indexedDB 基礎概念。在 indexedDB 裡面會根據索引 index 來進行整體數據結構的劃分。

indexedDB 資料庫的更新是一個非常蛋疼的事情,因為,Web 的靈活性,你既需要做好向上版本的更新,也需要完善向下版本的容錯性。

indexedDB 高效索引機制,在內部,indexedDB 已經提供了 、 等高效的索引機制,推薦不要直接將所有數據都取回來,再進行篩選,而是直接利用 進行。

最後推薦幾個常用庫


IndexedDB 可以存儲非常多的數據,比如 Object,files,blobs 等,裡面的存儲結構是根據 Database 來進行存儲的。每個 DB 裡面可以有不同的 object stores。具體結構如下圖:

並且,我們可以給 設定相關特定的值,然後在索引的時候,可以直接通過 key 得到具體的內容。使用 IndexDB 需要注意,其遵循的是同域原則。

在 indexDB 中,有幾個基本的操作對象:

Database: 通過 方法直接打開,可以得到一個實例的 DB。每個頁面可以創建多個 DB,不過一般都是一個。

Object store: 這個就是 DB 裡面具體存儲的對象。這個可以對應於 SQL 裡面的 table 內容。其存儲的結構為:

index: 有點類似於外鏈,它本身是一種 Object store,主要是用來在本體的 store 中,索引另外 object store 裡面的數據。需要區別的是,key 和 index 是不一樣的。可以參考: index DEMO,mdn index。如下圖表示:

如下 code 為:

transaction: 事務其實就是一系列 CRUD 的集合內容。如果其中一個環節失敗了,那麼整個事務的處理都會被取消。例如:

cursor: 主要是用來遍歷 DB 裡面的數據內容。主要是通過 來進行控制。


上面說了幾個基本的概念。那接下來我們實踐一下 IndexDB。實際上入門 IndexDB 就是做幾個基本的內容

打開資料庫表

設置指定的 primary Key

定義好索引的 index

前期搭建一個 IndexedDB 很簡單的代碼如下:

上面主要做了 3 件事:

打開資料庫表

新建 Store,並設置 primary Key

設置 index

打開資料庫表主要就是版本號和名字,沒有太多講的,我們直接從創建 store 開始吧。


使用的方法就是 IDBDatabase 上的 方法。

基本函數構造為:

keyPath: 用來設置主鍵的 key,具體區別可以參考下面的 keyPath 和 generator 的區別。

autoIncrement: 是否使用自增 key 的特性。

創建的 key 主要是為了保證,在數據插入時唯一性的標識。

不過,往往一個主鍵(key),是沒辦法很好的完成索引,在具體實踐時,就還需要輔鍵 (aid-key) 來完成輔助索引工作,這個在 IndexDB 就映射為 。


在完成 PK(Primary key) 創建完畢後,為了更好的搜索性能我們還需要額外創建 。這裡可以直接使用:

indexName: 設置當前 index 的名字

property: 從存儲數據中,指明 index 所指的屬性。

其中,options 有三個選項:

unique: 當前 key 是否能重複 (最常用)

multiEntry: 設置當前的 property 為數組時,會給數組裡面每個元素都設置一個 index 值。

具體可以參考:MDN createIndex Prop 和 googleDeveloper Index。


在 IndexedDB 裡面進行數據的增刪,都需要在 中完成。而這個增刪數據,大家可以理解為一次 ,相當於在一個 裡面管理所有當前邏輯操作的 。所以,在正式開始進行數據操作之前,還需要給大家簡單介紹一些如果創建一個事務。

事務的創建

API,如下 [代碼1]。在創建時,你需要手動指定當前 transaction 是那種類型的操作,基本的內容有:

"readonly":只讀

"readwrite":讀寫

"versionchange":這個不能手動指定,會在 回調事件裡面自動創建。它可以用來修改現有 object store 的結構數據,比如 index 等。

你可以通過在資料庫打開之後,通過 上的 方法創建,如 [代碼2]。

事務在創建的時候不僅僅可以制定執行的模式,還可以指定本次事務能夠影響的 ObjectStore 範圍,具體細節就是在第一個 參數裡面傳入的是一個數據,然後通過 方法打開多個 OS 進行操作,如下 [代碼3]。

操作數據

完成了事務的創建之後,我們就可以正式的開始進行數據的交互操作了,也就是寫我們具體的業務邏輯。如下 [代碼1],一個完整數據事務的操作。

通過 回調得到的 IDBObjectStore 對象,我們就可以進行一些列的增刪查改操作了。可以參考 [代碼2]。詳細的可以參考文末的 。

索引數據

索引數據是所有資料庫裡面最重要的一個。這裡,我們可以使用游標,index 來做。例如,通過 index 來快速索引 key 值,參考 [代碼1]。

更詳細的內容,可以參考下文 數據索引方式。


何謂 keyPath 和 keyGenerator 應該算是 IndexedDB 裡面比較難以理解的概念。簡單來說,IndexedDB 在創建 Store 的時候,必須保證裡面的數據是唯一的,那麼得需要像其它資料庫一樣設置一個 來區分不同數據。而 keyPath 和 Generator 就是兩種不同的設置 key 的方式。

設置 keyPath

因為 ssn 在該數據集是唯一的,所以,我們可以利用它來作為 保證 的特性。或者,可以設置為自增的鍵值,比如 類似的。

使用 generator

generator 會每次在添加數據時,自動創建一個 unique value。這個 unique value 是和你的實際數據是分開的。裡面直接通過 來設置即可。


檢查是否支持 indexDB

版本更新: indexDB

在生成一個 indexDB 實例時,需要手動指定一個版本號。而最常用的

這樣會造成一個問題,比如上線過程中,用戶A第一次請求返回了新版本的網頁,連接了版本2。之後又刷新網頁命中了另一台未上線的機器,連接了舊版本1 出錯。主要原因是:

indexedDB API 中不允許資料庫中的數據倉庫在同一版本中發生變化. 並且當前 DB 版本不能和低版本的 version 連接。

比如,你一開始定義的 DB 版本內容為:

如果此時,用戶先打開了 version(1),但是後面,又得到的是 version(2) 版本的 HTML,這時就會出現 error 的錯誤。

參考:

版本更替


這個在 IndexDB 是一個很重要的問題。主要原因在於

indexedDB API 中不允許資料庫中的數據倉庫在同一版本中發生變化. 並且當前 DB 版本不能和低版本的 version 連接。

上面就可以抽象為一個問題:

你什麼情況下需要更新 IndexDB 的版本呢?

該表資料庫裡面的 時。

你需要重新設計資料庫表結構時,比如新增 index

不過,如果直接修改版本號,會出現這樣一個 case:

由於原始 HTML 更新問題,用戶首先訪問的是版本 1 的 A 頁面,然後,訪問更新過後的 B 頁面。這時,IndexDB 成功更新為高版本。但是,用戶下次又命中了老版本的 A 頁面,此時 A 中還是連接低版本的 IndexDB ,就會報錯,導致你訪問失敗。

解決辦法就是,設置過濾,在 的時候,手動傳入版本號:

不過,這樣又會造成另外一個問題,即,數據遷移(老版本數據,不可能不要吧)。這裡,IndexDB 會有一個 updateCallback 給你觸發,你可以直接在裡面做相關的數據遷移處理。

在使用的時候,一定要注意 DB 版本的升級處理,比如有這樣一個 case,你的版本已經是 3,不過,你需要處理版本二的數據:

對於存在版本 2 資料庫的用戶來說是 OK 的,但是對於某些還沒有訪問過你資料庫的用戶來說,這無疑就報錯了。解決辦法有:

保留每個版本時,創建的欄位和 stores

在更新 callback 裡面,對處理的數據判斷是否存在即可。

在 Dexie.js DB 資料庫中,需要你保留每次 DB 創建的方法,實際上是通過 添加 swtich case ,來完成每個版本的更新:

如果遇到一個頁面打開,但是另外一個頁面拉取到新的代碼進行更新時,這個時候還需要將低版本 indexedDB 進行顯式的關閉。具體操作辦法就是監聽 事件,當版本升級時,通知當前 DB 進行關閉,然後在新的頁面進行更新操作。

最後,更新是還有幾個注意事項:

版本更新不能改變 primary key

回退代碼時,千萬注意版本是否已經更新。否則,只能增量更新,重新修改版本號來修復。


有時候,我們存儲時,想得到一個由一串 String 生成的 hash key,那在 Web 上應該如何實現呢?

這裡可以直接利用 Web 上已經實現的 WebCrypto,為了實現上述需求,我們可以直接利用裡面的 方法即可。這裡 MDN 上,已經有現成的辦法,我們直接使用即可。

參考:

WebCrypto 加密手段


基本限制為:

逐出策略為:

參考:

存儲上限值瀏覽器內核存儲上限值處理


在資料庫中除了基本的 CRUD 外,一個高效的索引架構,則是裡面的重中之重。在 indexedDB 中,我們一共可以通過三種方式來索引數據:

固定的 key 值

索引外鍵(index)

游標(cursor)


IDBObjectStore 提供給了我們直接通過 來索引數據,參考 [代碼1],這種方式需要我們一開始就知道目標的 內容。當然,也可以通過 全部索引數據。

比如,我們通過 primaryKey 得到一條具體的數據:

也可以 fetch 整個 Object Store 的數據。這些場景用處比較少,這裡就不過多講解。我們主要來了解一下 index 的索引方式。


如果想要查詢某個數據,直接通過整個對象來進行遍歷的話,這樣做性能耗時是非常大的。如果我們結合 來將 key 加以分類,就可以很快速的實現指定數據的索引。這裡,我們可以直接利用 IDBObjectStore 上面的 方法來獲取指定 index 的值,具體方法可以參考 [代碼1]。

該方法會直接返回一個 IDBIndex 對象。這你也可以理解為一個類似 ObjectStore 的微型 index 數據內容。接著,我們可以使用 方法來獲得指定 index 的數據,參考[代碼2]。

使用 方法不管你的 index 是否是 的都會只會返回第一個數據。如果想得到多個數據的話,可以使用 來做。通過 得到的回調函數,直接通過 可以得到對應的 value 內容。

除了通過 得到所有數據外,還可以採用更高效的 方法遍歷得到的數據。

參考:

getAll() 和 openCursor 實例


所謂的游標,大家心裡應該可以有一個初步的印象,就像我們物理尺子上的那個東西,可以自由的移動,來標識指向的對象內容。cursor 裡面有兩個核心的方法:

advance(count): 將當前游標位置向前移動 count 位置

continue(key): 將當前游標位置移動到指定 key 的位置,如果沒提供 key 則代表的移動下一個位置。

比如,我們使用 cursor 來遍歷 Object Store 的具體數據。

通常,游標可以用來遍歷兩個類型的數據,一個是 ObjectStore、一個是 Index。

Object.store: 如果在該對象上使用游標,那麼會根據 遍歷整個數據,注意,這裡不會存在重複的情況,因為 是唯一的。

index: 在 index 上使用游標的話,會以當前的 index 來進行遍歷,其中可能會存在重複的現象。

在 IDBObjectStore 對象上有兩種方法來打開游標:

openCursor: 遍歷的對象是 具體的數據值,最常用的方法

openKeyCursor: 遍歷的對象是 數據 key 值

這裡,我們通過 來直接打開一個 index 數據集,然後進行遍歷。

在游標中,還提供給了一個 和 方法,我們可以用它來進行數據的更新操作,否則的話就直接使用 ObjectStore 提供的 方法。

游標裡面我們還可以限定其遍歷的範圍和方向。這個設置是我們直接在 方法裡面傳參完成的,該方法的構造函數參考 [代碼1]。他裡面可以傳入兩個參數,第一個用來指定範圍,第二個用來指定 移動的方向。

如果需要對 cursor 設置範圍的話,就需要使用到 這個對象,使用樣板可以參考 [代碼2]。IDBKeyRange 裡面 key 參考的對象 因使用者的不同而不同。如果是針對 ObjectStore 的話,則是針對 primaryKey,如果是針對 Index 的話,則是針對當前的 indexKey

比如,我們這裡對 PersonIndex 設置一個 index 範圍,即,索引 在 和 之間的數據集合。

如果你還想設置遍歷的方向和是否排除重複數據,還可以根據 [代碼2] 的枚舉類型來設置。比如,在 [代碼3] 中,我們改變默認的 cursor 遍曆數據的方向為 ,從末尾開始。


在 indexDB 裡面的讀寫全部是基於 模式來的。也就是 IDBDataBase 裡面的 方法,如下 [代碼1]。所有的讀寫都可以比作在 作用域下的請求,只有當所有請求完成之後,該次 才會生效,否則就會拋出異常或者錯誤。 會根據監聽 error,abort,以及 complete 三個事件來完成整個事務的流程管理,參考[代碼2]。

例如:

你可以在 方法裡面手動傳入 或者其他表示事務的 參數,來表示本次事務你會進行如何的操作。IndexedDB 在初始設計時,就已經決定了它的性能問題。

只含有 readonly 模式的 transaction 可以並發進行執行含有 write 模式的 transaction 必須按照隊列 來 執行

這就意味著,如果你使用了 模式的話,那麼後續不管是不是 都必須等待該次 transaction 完成才行。


指定 primaryKey 生成時,是通過 方法來操作的。有時候,我們會遇到想直接得到一個 key,並且存在於當前數據集中,可以在 options 中同時加上 和 屬性。該 key 的範圍是 [1- $ 2^ $],參考 keygenerator key 的大小


閱讀推薦

indexedDB W3C 文檔 indexedDB 入門MDN indexedDB 入門

好用庫推薦

idb: 一個 promise 的 DB 庫


IndexedDB 資料庫使用key-value鍵值對儲存數據.你可以對對象的某個屬性創建索引(index)以實現快速查詢和列舉排序。.key可以使二進位對象

IndexedDB 是事務模式的資料庫. IndexedDB API提供了索引(indexes), 表(tables), 指針(cursors)等等, 但是所有這些必須是依賴於某種事務的。

The IndexedDB API 基本上是非同步的.

IndexedDB 資料庫的請求都會包含 onsuccess和onerror事件屬性。

IndexedDB 在結果準備好之後通過DOM事件通知用戶

IndexedDB是面向對象的。indexedDB不是用二維表來表示集合的關係型資料庫。這一點非常重要,將影響你設計和建立你的應用程序。

indexedDB不使用結構化查詢語言(SQL)。它通過索引(index)所產生的指針(cursor)來完成查詢操作,從而使你可以迭代遍歷到結果集合。

IndexedDB遵循同源(same-origin)策略


全球多種語言混合存儲。國際化支持不好。需要自己處理。

和伺服器端資料庫同步。你得自己寫同步代碼。

全文搜索。

在以下情況下,資料庫可能被清除:

用戶請求清除數據。

瀏覽器處於隱私模式。最後退出瀏覽器的時候,數據會被清除。

硬碟等存儲設備的容量到限。

不正確的

不完整的改變.


資料庫

資料庫: 通常包含一個或多個 object stores. 每個資料庫必須包含以下內容:

名字(Name): 它標識了一個特定源中的資料庫,並且在資料庫的整個生命周期內保持不變. 此名字可以為任意字元串值(包括空字元串).

當前版本(version). 當一個資料庫首次創建時,它的 version 為1,除非另外指定. 每個資料庫在任意時刻只能有一個 version

對象存儲(object store): 用來承載數據的一個分區.數據以鍵值對形式被對象存儲永久持有。在 OS 中,創建一個 key 可以使用 和 。

key generator: 簡單來說就是在存儲數據時,主動生成一個 id++ 來區分每條記錄。這種情況下 存儲數據的 key 是和 value 分開進行存儲的,也就是 (out of line)。

key path: 需要用戶主動來設置儲存數據的 key 內容,

request: 每次讀寫操作,可以當做一次 request.

transaction: 一系列讀寫請求的集合。

index: 一個特殊的 Object Store,用來索引另外一個 Store 的數據。

具體數據 key/value

key generator: 相當於以一種 的形式來生成一個 key 值。

key path: 當前指定的 key 可以根據 value 裡面的內容來指定。裡面可以為一些分隔符。

指定的 key:這個就是需要用戶手動來指定生成。

key: 這個 key 的值,可以通過三種方式生成。 a key generator, a key path, 用戶指定的值。並且,這個 key 在當前的 Object Store 是唯一的。一個 key 類型可以是 string, date, float, and array 類型。不過,在老版本的時候,一般只支持 string or integer。(現在,版本應該都 OK 了)

value: 可以存儲 boolean, number, string, date, object, array, regexp, undefined, and null。現在還可以存儲 files and blob 對象。

操作作用域

scope:這可以比作 transaction 的作用域,即,一系列 transaction 執行的順序。該規定,多個 reading transaction 能夠同時執行。但是 writing 則只能排隊進行。

key range: 用來設置取出數據的 key 的範圍內容。

參考:

原生概念 IndexedDB


這其實就是 上面掛載的對象。主要 API 如下:

你可以直接通過 來打開一個資料庫。通過 返回一個 Request 對象,來進行結果監聽的回調:

參考:

IndexDB Factory API


當你通過 方法處理過後,就會得到一個 Request 回調對象。這個就是 IDBRequest 的實例。

你可以通過 得到當前資料庫操作的結果。如果你打開更新後的版本號的話,還需要監聽 事件來實現。最常通過 indexedDB.open 遇見的錯誤就是 版本錯誤。這表明存儲在磁碟上的資料庫的版本高於你試圖打開的版本。

所以,一般在創建 IndexDB 時,還需要管理它版本的更新操作,這裡就需要監聽 onupgradeneeded 來是實現。

或者我們可以直接使用 微型庫來實現讀取操作。

其中通過 回調得到的 event.result 就是 的實例,常常用來設置 index 和插入數據。參考下面內容。

參考:

IDBRequest API


該對象常常用來做 Object Store 和 transaction 的創建和刪除。該部分是 事件獲得的 對象:

具體 API 內容如下:

如果它通過 createObjectStore 方法,那麼得到的就是一個 實例對象。如果是 transaction 方法,那麼就是 對象。


該對象一般是用來創建 index 和插入數據使用。

可以參考:


該對象是用來進行 Index 索引的操作對象,裡面也會存在 和 等方法。詳細內容如下:

參考:

idb 開源庫,微型代碼庫treo 開源庫dexie.js 開源庫indexeddb原生概念 IndexedDB


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端小吉米 的精彩文章:

TAG:前端小吉米 |