javascript
JavaScript IndexedDB 完整指南
JavaScript IndexedDB 完整指南
- JavaScript IndexedDB 完整指南
- 1. 瀏覽器存儲方式
- 2. 使用案例
- 3. 性能和其他考慮因素
- 4. 小結
JavaScript IndexedDB 完整指南
本文將通過一個小教程向你介紹 IndexedDB,并將 IndexedDB 與其他可用選項進行比較。IndexedDB 用于在瀏覽器中存儲數據,對于需要離線工作的 web 應用程序(如大多數進步的 web 應用程序)尤其重要。
首先,讓我們介紹一下為什么需要將數據存儲在 web 瀏覽器中。數據在 web 應用程序中無處不在——用戶交互創建數據、查找數據、更新數據和刪除數據。如果沒有存儲這些數據的方法,就不可能允許用戶交互跨多個 web 應用程序的使用保持狀態。你通常會使用 MySQL、Postgres、MongoDB、Neo4j、ArangoDB 等數據庫來處理這些存儲,但如果你希望應用程序脫機工作呢?
這在不斷發展的 web 應用程序中尤為重要,這些應用程序復制了原生應用程序的感覺,但卻位于瀏覽器中。這些漸進的 web 應用程序必須離線工作,因此需要一個存儲選項。幸運的是,有幾種關于如何在瀏覽器中存儲數據的工具,可以在線和離線訪問數據。
1. 瀏覽器存儲方式
關于如何在瀏覽器中存儲數據,Web 標準提供了三個主要 API:
- Cookies:此數據存儲在瀏覽器中,Cookies 的大小限制為 4k。通常當服務器響應一個請求時,它們可能包含一個 SET-COOKIE 頭,給瀏覽器一個要存儲的鍵和值。然后,客戶端應該在未來的請求頭中包含這個 cookie,這將允許服務器識別瀏覽器會話等。這些 cookie 通常具有 HTTP-Only 屬性,這意味著不能通過客戶端腳本訪問 cookie。這使得 cookie 不是保存脫機數據的好選擇。
- LocalStorage/SessionStorage:LocalStorage / SessionStorage是瀏覽器內置的鍵值存儲,其中每個鍵的大小限制為 5MB。LocalStorage 存儲數據,直到刪除為止,而 sessionStorage 將在瀏覽器關閉時清除自己。除此之外,它們的 API 是相同的??梢允褂?window.localStorage.setItem("Key", "Value") 添加鍵值對。并使用 window.localStorage.getItem("Key") 檢索一個值。注意, LocalStorage API 是同步的,因此使用它會阻塞瀏覽器中的其他活動,這可能是一個問題。你可以閱讀 JavaScript LocalStorage 完整指南 了解更多關于 LocalStorage 的信息。
- IndexedDB:一個內置在瀏覽器中的完整文檔數據庫,沒有存儲限制,它允許你異步訪問數據,這對于防止復雜操作阻塞呈現和其他活動非常有效。這就是我們將在下面深入討論的內容。
在這些方式中,localStorage 是進行簡單操作和存儲少量數據的好選擇。對于更復雜或常規的操作,IndexedDB 可能是更好的選擇,特別是在需要異步獲取數據的情況下。
IndexedDB API 比 LocalStorage API 更復雜。所以,讓我們用 IndexedDB 構建一些東西,讓你更好地感受它是如何工作的!
2. 使用案例
創建一個新的 HTML 文件,我們稱之為 index.html,內容如下:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>IndexedDB Todo List</title><style>body {text-align: center;}h1 {color: brown;}</style> </head> <body><main><h1>IndexedDB Todo-List</h1><div id="form"><input type="text" placeholder="new todo here"><button>Add Todo</button></div><div id="todos"><ul></ul></div></main><script>// 保存輸入的變量const textInput = document.querySelector("[type='text']")const button = document.querySelector("button")// 保存 todos 的數組const todos = []// 渲染 todos 的函數function renderTodos(){const ul = document.querySelector("#todos ul")ul.innerHTML = ""for (todo of todos){ul.innerHTML += `<li>${todo}</li>`}}renderTodos()</script></body> </html>現在我們可以開始設置 IndexedDB 了。在瀏覽器中打開此文件。如果你正在使用 VS Code,可以用像 liveserver 這樣的擴展。
IndexedDB 支持非常好,但我們仍然想檢查瀏覽器是否支持 API 的實現,以便你可以添加以下函數來檢查。
// 檢查 indexedDB 實現并返回它的函數 function getIndexDB() {const indexedDB =window.indexedDB ||window.mozIndexedDB ||window.webkitIndexedDB ||window.msIndexedDB ||window.shimIndexedDB;if (indexedDB){return indexedDB}console.error("indexedDB not supported by this browser")return null }這個函數要么返回 IndexedDB 的瀏覽器實現,要么返回瀏覽器不支持的日志。你可以記錄在瀏覽器中調用 getIndexDB 的結果,以確認瀏覽器支持 IndexedDB。
下面你可以看到兼容性列表。你可以在這里找到完整的列表,包括移動瀏覽器。
現在讓我們用 indexedDB.open("database name", 1) 打開一個數據庫。open 的第一個參數是數據庫的名稱,第二個參數是數據庫的版本。如果你希望觸發一個 onupgraderequired,你應該在 .open 調用中增加版本號。open 方法將返回一個具有多個屬性的對象,包括 onerror、onupgradenneeded 和 onsuccess,每個屬性都接受一個回調函數,在相關事件發生時執行。
const indexedDB = getIndexDB() // console.log(indexedDB) const request = indexedDB.open("todoDB", 1) console.log(request) renderTodos();你應該看到一個 console.log,其中顯示一個 IDBOpenDBRequest 對象。IndexedDB 是基于事件的,這符合它的異步模型。接下來,讓我們看看數據庫啟動時可能發生的事件。首先,我們將監聽request.onerror事件,以防訪問數據庫時出現任何錯誤。
const indexedDB = getIndexDB() // console.log(indexedDB) const request = indexedDB.open("todoDB", 1) //console.log(request) // onerror 處理 request.onerror = (event) => console.error("IndexDB Error: ", event)renderTodos();我們將監聽的下一個事件是 request.onupgradeneeded 事件,當試圖打開一個版本號高于數據庫當前版本號的數據庫時,該事件就會運行。這是創建存儲 / 表及其模式的函數。這個函數在每個版本號下只執行一次。因此,如果你決定更改 onupgradedened 回調來更新你的模式或創建新的存儲,那么版本號也應該在下一個 .open 調用中增加。存儲本質上相當于傳統數據庫中的表。
const indexedDB = getIndexDB(); // console.log(indexedDB) const request = indexedDB.open("todoDB", 1); //console.log(request) //onerror handling request.onerror = (event) => console.error("IndexDB Error: ", event); //onupgradeneeded request.onupgradeneeded = () => {// 獲取數據庫連接const db = request.result;// 定義一個新存儲const store = db.createObjectStore("todos", {keyPath: "id",autoIncrement: true,});// 指定一個屬性作為索引store.createIndex("todos_text", ["text"], {unique: false}) };renderTodos();在 onupgradeneeded 中,我們做了以下幾點:
- 獲取數據庫對象(如果 onupgradenneeded 函數正在運行,你就知道它是可用的)
- 創建一個名為 todos 的新存儲 / 表 / 集合,其鍵 id 是一個自動遞增的數字(記錄的唯一標識符)
- 指定 todos_text 作為索引,這允許我們稍后通過 todos_text 搜索數據庫。如果不打算按特定屬性進行搜索,則不必創建索引。
最后要處理 request.onsuccess 事件,該事件在數據庫連接和存儲全部設置和配置之后運行。你可以利用這個機會提取 todo 列表并將它們注入到我們的數組中。
//onsuccess request.onsuccess = () => {console.log("Database Connection Established")// 獲取數據庫連接const db = request.result// 創建事務對象const tx = db.transaction("todos", "readwrite")// 創建一個與我們存儲的事務const todosStore = tx.objectStore("todos")// 得到所有待辦事項const query = todosStore.getAll()// 使用數據查詢query.onsuccess = () => {console.log("All Todos: ", query.result)for (todo of query.result){todos.push(todo.text)}renderTodos()} }在 onsuccess 中,我們做了以下幾點:
- 獲取數據庫連接
- 創建事務
- 指定我們在哪個存儲上進行事務處理
- 運行一個 getAll 查詢來獲取存儲中的所有文檔 / 記錄
- 在查詢特定的 onsuccess 事件中,我們循環遍歷 todos,將它們存入 todos 數組并調用 renderTodos(),因此它們被渲染到 dom 中
你應該在控制臺中看到一個 console.log,其中包含一個空數組。
**錯誤提示:**如果你正在運行一個熱重新加載 web 服務器,如 liveserver,你可能會看到一個錯誤,沒有存儲。這是因為 onupgradedneeded 函數在你寫完函數之前就執行了。因此,它不會為該版本號再次執行。解決方案是增加表的版本號,這將創建一個 onupgradenneeded,并且 onupgradenneeded 回調將在下次頁面刷新時執行。
現在我們已經有了數據庫設置,可以對我們希望發生的任何其他事件遵循相同的模式。例如,讓我們在單擊按鈕時創建一個事件,該事件不僅會向 dom 添加一個新的 todo,還會向數據庫添加一個新的 todo,以便在頁面刷新時顯示。
// button 事件 button.addEventListener("click", (event) => {// 設置一個事務const db = request.resultconst tx = db.transaction("todos", "readwrite")const todosStore = tx.objectStore("todos")// 增加一個 todoconst text = textInput.valuetodos.push(text) // 增加一個 todo 到數組todosStore.put({text}) // 添加到 indexedDBrenderTodos() // 更新 dom })現在你可以添加 todos,因為你使用的是 IndexedDB,無論你是在線還是離線,它都可以工作。
添加一些 todo,當你刷新頁面時,你將看到 todo 持續存在。它們也會顯示在查詢結果的 console.log 中,每個 todo 都有一個唯一的 ID。到目前為止,完整的代碼應該如下所示:
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>IndexedDB Todo List</title><style>body {text-align: center;}h1 {color: brown;}</style></head><body><main><h1>IndexedDB Todo-List</h1><div id="form"><input type="text" placeholder="new todo here" /><button>Add Todo</button></div><div id="todos"><ul></ul></div></main><script>// 保存輸入的變量const textInput = document.querySelector("[type='text']");const button = document.querySelector("button");// 保存 todos 的數組const todos = [];// 渲染 todos 的函數function renderTodos() {const ul = document.querySelector("#todos ul");ul.innerHTML = "";for (todo of todos) {ul.innerHTML += `<li>${todo}</li>`;}}// 檢查 indexedDB 實現并返回它的函數function getIndexDB() {const indexedDB =window.indexedDB ||window.mozIndexedDB ||window.webkitIndexedDB ||window.msIndexedDB ||window.shimIndexedDB;if (indexedDB) {return indexedDB;}console.log("indexedDB not supported by this browser");return null;}const indexedDB = getIndexDB();// console.log(indexedDB)const request = indexedDB.open("todoDB", 2);// console.log(request)// onerror 處理request.onerror = (event) => console.error("IndexDB Error: ", event);// onupgradeneededrequest.onupgradeneeded = () => {// 獲取數據庫連接const db = request.result;// 定義一個新存儲const store = db.createObjectStore("todos", {keyPath: "id",autoIncrement: true,});// 指定一個屬性作為索引store.createIndex("todos_text", ["text"], {unique: false})};// onsuccessrequest.onsuccess = () => {console.log("Database Connection Established")// 獲取數據庫連接const db = request.result// 創建事務對象const tx = db.transaction("todos", "readwrite")// 創建一個我們的存儲事務const todosStore = tx.objectStore("todos")// 獲取所有 todoconst query = todosStore.getAll()// 使用數據查詢query.onsuccess = () => {console.log("All Todos: ", query.result)for (todo of query.result){todos.push(todo.text)}renderTodos()}}// button 事件button.addEventListener("click", (event) => {// 設置一個事務const db = request.resultconst tx = db.transaction("todos", "readwrite")const todosStore = tx.objectStore("todos")// 添加一個 todoconst text = textInput.valuetodos.push(text) // 添加 todo 到數組todosStore.put({text}) // 添加到 indexedDBrenderTodos() // 更新 dom})renderTodos();</script></body> </html>todosStore 對象上可用于不同類型事務的其他方法:
- clear: 刪除 store 中的所有記錄
- add:用給定的 id 插入一個記錄(如果它已經存在就會出錯)
- put:用給定的 id 插入或更新一個記錄(如果已經存在就會更新)
- get:用特定的 id 獲取記錄
- getAll:從 store 中獲取所有記錄
- count:返回 store 中的記錄數
- createIndex:基于給定的 index創建對象來查詢
- delete: 對給定 id 進行刪除記錄
3. 性能和其他考慮因素
你需要考慮以下幾點:
- 并不是所有瀏覽器都支持將文件存儲為 blob,你會發現更好的方式:將它們存儲為 arraybuffer。
- 有些瀏覽器可能不支持在私人瀏覽模式下寫入 IndexedDB
- IndexedDB 在寫入對象時會創建結構化克隆,這會阻塞主線程,所以如果你的大對象中填充了更多嵌套的對象,這可能會導致一些延遲。
- 如果用戶關閉瀏覽器,則任何未完成的事務都有可能被中止。
- 如果另一個瀏覽器選項卡打開了一個更新的數據庫版本號的應用程序,它將被阻止升級,直到所有舊版本選項卡關閉 / 重新加載。幸運的是,你可以使用 onblocked 事件來觸發警報,通知用戶他們需要這樣做。
你可以在 MDN 文檔中找到更多 IndexedDB 的限制。
雖然 indexedDB 非常適合讓你的應用程序離線工作,但它不應該成為你的主數據存儲。在互聯網連接中,你可能希望將 indexedDB 與外部數據庫同步,以便在用戶清除瀏覽器數據時不會丟失用戶的信息。
4. 小結
IndexedDB 在瀏覽器中為你提供了一個功能強大的異步文檔數據庫。IndexedDB API 可能有點麻煩,但是像 Dexie 這樣的庫可以為你提供 IndexedDB 的包裝器,使用起來要容易得多。
總結
以上是生活随笔為你收集整理的JavaScript IndexedDB 完整指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Crystal Reports - Ad
- 下一篇: 虚幻引擎学习总结(其一)