本文将通过一个小教程向你介绍 IndexedDB
,并将 IndexedDB
与其他可用选项进行比较。IndexedDB
用于在浏览器中存储数据,对于需要离线工作的 web 应用程序(如大多数进步的 web 应用程序)尤其重要。
首先,让我们介绍一下为什么需要将数据存储在 web 浏览器中。数据在 web 应用程序中无处不在 —— 用户交互创建数据、查找数据、更新数据和删除数据。如果没有存储这些数据的方法,就不可能允许用户交互跨多个 web 应用程序的使用保持状态。你通常会使用 MySQL、Postgres、MongoDB、Neo4j、ArangoDB 等数据库来处理这些存储,但如果你希望应用程序脱机工作呢?
这在不断发展的 web 应用程序中尤为重要,这些应用程序复制了原生应用程序的感觉,但却位于浏览器中。这些渐进的 web 应用程序必须离线工作,因此需要一个存储选项。幸运的是,有几种关于如何在浏览器中存储数据的工具,可以在线和离线访问数据。
关于如何在浏览器中存储数据,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
构建一些东西,让你更好地感受它是如何工作的!
创建一个新的 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.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
进行删除记录你需要考虑以下几点:
blob
,你会发现更好的方式:将它们存储为 arraybuffer
。IndexedDB
IndexedDB
在写入对象时会创建结构化克隆,这会阻塞主线程,所以如果你的大对象中填充了更多嵌套的对象,这可能会导致一些延迟。onblocked
事件来触发警报,通知用户他们需要这样做。你可以在 MDN 文档中找到更多 IndexedDB
的限制。
虽然 indexedDB
非常适合让你的应用程序离线工作,但它不应该成为你的主数据存储。在互联网连接中,你可能希望将 indexedDB
与外部数据库同步,以便在用户清除浏览器数据时不会丢失用户的信息。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。