JavaScript IndexedDB 完整指南
本文將透過一個crmeb小教程向你介紹
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 是同步的,因此使用它會阻塞瀏覽器中的其他活動,這可能是一個問題。 -
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,可以用像
這樣的擴充套件。
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 handlingrequest.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}) };renderTodos();複製程式碼
在
onupgradeneeded
中,我們做了以下幾點:
- 獲取資料庫物件(如果
onupgradenneeded
函式正在執行,你就知道它是可用的) - 建立一個名為
todos
的新儲存 / 表 / 集合,其鍵id
是一個自動遞增的數字(記錄的唯一識別符號) - 指定
todos_text
作為索引,這允許我們稍後透過todos_text
搜尋資料庫。如果不打算按特定屬性進行搜尋,則不必建立索引。
最後要處理
request.onsuccess
事件,該事件在資料庫連線和儲存全部設定和配置之後執行。你可以利用這個機會提取
todo
列表並將它們注入到我們的陣列中。
//onsuccessrequest.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.result const tx = db.transaction("todos", "readwrite") const todosStore = tx.objectStore("todos") // 增加一個 todo const text = textInput.value todos.push(text) // 增加一個 todo 到陣列 todosStore.put({text}) // 新增到 indexedDB renderTodos() // 更新 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); // onupgradeneeded request.onupgradeneeded = () => { // 獲取資料庫連線 const db = request.result; // 定義一個新儲存 const store = db.createObjectStore("todos", { keyPath: "id", autoIncrement: true, }); // 指定一個屬性作為索引 store.createIndex("todos_text", ["text"], {unique: false}) }; // onsuccess request.onsuccess = () => { console.log("Database Connection Established") // 獲取資料庫連線 const db = request.result // 建立事務物件 const tx = db.transaction("todos", "readwrite") // 建立一個我們的儲存事務 const todosStore = tx.objectStore("todos") // 獲取所有 todo const 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.result const tx = db.transaction("todos", "readwrite") const todosStore = tx.objectStore("todos") // 新增一個 todo const text = textInput.value todos.push(text) // 新增 todo 到陣列 todosStore.put({text}) // 新增到 indexedDB renderTodos() // 更新 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
與外部資料庫同步,以便在使用者清除瀏覽器資料時不會丟失使用者的資訊。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70021881/viewspace-2916986/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- IndexedDB使用與出坑指南Index
- Windows Terminal完整指南Windows
- GOOGLE RANKBRAIN 完整指南GoAI
- IndexedDBIndex
- 【譯】JavaScript 完整手冊JavaScript
- MPLS的完整工作指南
- 初探IndexedDBIndex
- JavaScript編碼指南JavaScript
- [譯] JavaScript 之 this 指南JavaScript
- Java版本安裝完整指南 - marcobehlerJava
- Flutter指南之環境完整搭建Flutter
- Java中實現GraphQL完整指南Java
- indexedDB入門Index
- IndexedDB upgradeneeded 事件Index事件
- indexedDB.deleteDatabase()IndexdeleteDatabase
- indexedDB 修改索引Index索引
- IndexedDB詳解Index
- JavaScript 日期權威指南JavaScript
- JavaScript 閉包基本指南JavaScript
- Google JavaScript 風格指南GoJavaScript
- React Native推送通知:完整的操作指南React Native
- Git 版本控制系統的完整指南Git
- Python開發網站的完整指南Python網站
- [譯] 快速,完整的 Mocha 測試指南
- 超引數最佳化完整指南
- API優先方法的完整指南 - ITNEXTAPI
- Airbnb JavaScript程式碼規範(完整)AIJavaScript
- IndexedDB(一:基本使用)Index
- indexedDB 初體驗Index
- indexedDB 更新資料Index
- indexedDB 新增資料Index
- indexedDB transaction 事務Index
- Node.js 事件迴圈的完整指南Node.js事件
- 業務流程管理BPM的完整指南 -frevvo
- Docker的ARG、ENV和.env配置完整指南Docker
- 前端必要懂的,完整的 HTTP cookie 指南前端HTTPCookie
- Web3 全棧開發完整指南Web全棧
- JavaScript編碼風格指南JavaScript