深入了解 IndexedDB
IndexedDB 是瀏覽器多年來引入的一種存儲功能之一。這是一個關於 IndexedDB 的介紹,這是一種由所有現代瀏覽器支持的 Web 數據庫。
- 介紹 IndexedDB
- 創建 IndexedDB 數據庫
- 將數據添加到數據庫中
- 從存儲中獲取數據
- 從 IndexedDB 中刪除數據
- 從之前的數據庫版本遷移
- 唯一鍵
- 從 IndexedDB 中刪除
- 還有更多!
介紹 IndexedDB
IndexedDB 是多年來引入瀏覽器的數據存儲能力之一。
它是一個鍵值存儲(NoSQL 數據庫),被認為是“在瀏覽器中存儲數據的終極解決方案”。
它是一個異步 API,這意味著執行昂貴的操作不會阻塞 UI 线程,為用戶提供了流暢的體驗。它可以存儲無限量的數據,但一旦超過一定閾值,用戶將被提示提高站點限制。
它在所有現代瀏覽器上都受支持。
它支持事務、版本控制並提供良好的性能。
在瀏覽器內部,我們也可以使用:
- Cookies:可以存儲大量字符串
- Web Storage(或 DOM Storage),這是一個常用來識別 localStorage 和 sessionStorage 的術語,它們都是鍵值存儲。sessionStorage 不會保留數據,當會話結束時,數據就被清除了,而 localStorage 會在不同的會話之間保留數據。
本地/會話存儲的缺點是被限制在一個小(且不一致)的大小,不同瀏覽器的實現為每個站點提供2MB到10MB的空間。
過去,我們還有 Web SQL,一個對 SQLite 的封裝。但現在 Web SQL 已經被棄用,在一些現代瀏覽器上不受支持,它從未被公認為標準,因此不應使用。然而,根據 Can I Use 的數據,83% 的用戶設備上有此技術。
雖然您可以在一個站點中創建多個數據庫,但通常您只創建一個數據庫,並在該數據庫內創建多個對象存儲。
數據庫對於域是私有的,因此其他網站無法訪問另一個網站的 IndexedDB 存儲。
每個存儲通常包含一組“物品”,這些物品可以是:
- 字符串
- 數字
- 對象
- 數組
- 日期
例如,您可能有一個存儲包含帖子,另一個存儲包含評論。
存儲包含多個項目,每個項目都具有唯一的鍵,鍵表示可以通過該方式標識對象的方式。
您可以使用事務來更改這些存儲,通過執行添加、編輯和刪除操作,並迭代它們包含的項目。
自從 ES6 中引入了 Promises 以及隨後將 API 移至使用 Promises,IndexedDB API 看起來有點“過時”。
雖然這沒有什麼問題,但在我即將解釋的所有示例中,我將使用 Jake Archibald 的 IndexedDB Promised Library,這是一個位於 IndexedDB API 之上的微小層,使其更容易使用。
Google 開發者網站上關於 IndexedDB 的所有示例也都使用了這個庫。
創建 IndexedDB 數據庫
最簡單的方法是使用 unpkg,將以下代碼添加到頁面標頭:
1 | <script type="module"> |
在使用 IndexedDB API 之前,請始終確保檢查瀏覽器是否支持。儘管它在各種瀏覽器上都得到了廣泛支持,但您永遠不知道用戶在使用哪個瀏覽器:
1 | (() => { |
如何創建數據庫
使用openDB()
:
1 | (async () => { |
前兩個參數是數據庫名稱和版本。第三個參數(可選)是一個對象,包含一個只有在版本號高於當前安裝的數據庫版本時才調用的函數。在函數體中,您可以升級數據庫的結構(存儲和索引)。
將數據添加到數據庫中
在創建存儲時添加數據,進行初始化
您可以使用對象存儲的 put
方法,但首先我們需要一個對它的引用,可以通過創建它時的 db.createObjectStore()
獲得。
當使用 put
時,值是第一個參數,鍵是第二個參數。這是因為如果在創建對象存儲時指定了 keyPath
,則您不需要在每個 put()
請求中輸入鍵名,只需編寫值。
這會在我們創建 store0
時立即填充它:
1 | (async () => { |
在存儲創建後添加數據,使用事務
要稍後添加項目,您需要創建一個讀/寫的事務,可以確保數據庫完整性(如果一個操作失敗,事務中的所有操作都將回滾,狀態回到已知狀態)。
為此,使用調用 openDB
時獲得的 dbPromise
對象的引用,並運行:
1 | (async () => { |
從存儲中獲取數據
從存儲中獲取一個項目:get()
1 | const key = 'Hello again' |
從存儲中獲取所有項目:getAll()
獲取存儲中的所有鍵
1 | const items = await db.transaction(storeName).objectStore(storeName).getAllKeys() |
獲取存儲中的所有值
1 | const items = await db.transaction(storeName).objectStore(storeName).getAll() |
從 IndexedDB 中刪除數據
刪除數據庫、對象存儲和數據
刪除整個 IndexedDB 數據庫
1 | const dbName = 'mydbname' |
刪除對象存儲中的數據
使用事務:
1 | (async () => { |
從之前的數據庫版本遷移
openDB()
函數的第三個(可選)參數是一個對象,可以包含一個 upgrade
函數,該函數只在版本號高於當前安裝的數據庫版本時才調用。在該函數體內,您可以根據需要升級數據庫的結構(存儲和索引):
1 | const name = 'mydbname' |
在此回調函數中,您可以檢查用戶正在進行更新的版本,並相應地執行一些操作。
您可以使用以下語法從過去的數據庫版本進行遷移:
1 | (async () => { |
唯一鍵
可如您在案例1
中所見,createObjectStore()
接受第二個參數,指示數據庫的索引鍵。這在存儲對象時非常有用:put()
調用不需要第二個參數,只需將值(對象)傳遞給它,並且鍵將映射到具有該名稱的對象屬性。
索引提供了一種通過特定鍵檢索後續值的方法,它必須是唯一的(每個項目必須具有不同的鍵)。
如果您的值中沒有唯一鍵(例如,如果您收集沒有相關聯名稱的電子郵件地址),則可以將鍵設置為自動增量,這樣您就不需要在客戶端代碼中跟踪它:
1 | db.createObjectStore('notes', { autoIncrement: true }) |
檢查存儲是否存在
您可以通過調用 objectStoreNames()
方法來檢查是否已經存在一個對象存儲:
1 | const storeName = 'store1' |
從 IndexedDB 中刪除
刪除數據庫、對象存儲和數據
刪除數據庫
1 | await deleteDB('mydb') |
刪除對象存儲
只能在打開數據庫時的回調函數中刪除對象存儲,並且只有在指定的版本高於當前安裝的版本時才調用該回調函數:
1 | const db = await openDB('dogsdb', 2, { |
要刪除對象存儲中的數據,請使用事務
1 | const key = 232 // 隨機鍵 |
還有更多!
這只是基礎部分。我沒有談到遊標和更高級的功能。IndexedDB 還有更多內容,但我希望這能夠讓你有一個良好的開始。