首页 > IT职业 > IT职场 > JavaScript IndexedDB 完整指南
本文将通过一个crmeb小教程向你介绍
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 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 进行删除记录
你需要考虑以下几点:
blob,你会发现更好的方式:将它们存储为
arraybuffer。IndexedDBIndexedDB 在写入对象时会创建结构化克隆,这会阻塞主线程,所以如果你的大对象中填充了更多嵌套的对象,这可能会导致一些延迟。onblocked 事件来触发警报,通知用户他们需要这样做。你可以在
MDN 文档中找到更多
IndexedDB 的限制。
虽然
indexedDB 非常适合让你的应用程序离线工作,但它不应该成为你的主数据存储。在互联网连接中,你可能希望将
indexedDB 与外部数据库同步,以便在用户清除浏览器数据时不会丢失用户的信息。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70021881/viewspace-2916986/ ,如需转载,请注明出处,否则将追究法律责任。