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/SessionStorageLocalStorage / SessionStorage是浏览器内置的键值存储,其中每个键的大小限制为 5MBLocalStorage 存储数据,直到删除为止,而 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 方法将返回一个具有多个属性的对象,包括 onerroronupgradenneededonsuccess,每个属性都接受一个回调函数,在相关事件发生时执行。

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 完整指南相关推荐

  1. 《javascript面向对象编程指南》读书笔记

    <javascript面向对象编程指南>读书笔记 <javascript面向对象编程指南>读书笔记 第一章 面向对象的JavaScript 第二章 基本数据类型与流程控制 变量 ...

  2. javascript立体学习指南

    javascript立体学习指南 第一章:首先了解javascript 首先,什么是javascript? JavaStrip出生于1995年,是一种文本脚本语言,成都装修公司是一种动态的.弱类型的. ...

  3. JavaScript => JavaScript编码规范指南

    JavaScript 编码规范指南 以下文档大多来自: Google JavaScript 编码规范指南 Idiomatic 风格 对于未提及的事项可以参考airbnb的JS编码规范 airbnb/j ...

  4. 干干!JavaScript学习路线指南,阅读本文即可

    干干!JavaScript学习路线指南,阅读本文即可 自习/学习路线这样的一期我想写很长时间,因为一直想写的全一点硬一点,所以拖到了现在,我相信这一期对于那些还在上学或者已经工作的同学来说是有帮助的, ...

  5. CSS 计算属性 calc()的完整指南(下)

    从之前的文章:CSS 计算属性 calc()的完整指南(一),我们可以学习到几个方面: calc() 只作用于属性值 calc() 用于长度和其他数值 不能在媒体查询中使用 混合单位 与预处理器数学比 ...

  6. 高级WordPress主题开发完整指南

    高级WordPress主题开发完整指南 从头开始创建高级 WordPress 主题.了解有关 WordPress 主题开发的所有信息.在 Themeforest 上获得批准 课程英文名:The Com ...

  7. 如何防止跨站点脚本 (XSS) 攻击完整指南

    跨站点脚本 (XSS) 攻击的完整指南.如何防止它以及 XSS 测试. 跨站点脚本 (XSS) 是每个高级测试人员都知道的最流行和易受攻击的攻击之一.它被认为是对 Web 应用程序最危险的攻击之一,也 ...

  8. 3ds Max V-Ray5 完整指南大师班视频教程

    3ds Max V-Ray5 完整指南大师班视频教程 时长15小时 包括项目文件 1920X1080 MP4 语言:英语+中文字幕(机译) 标题:Gumroad–V-Ray 5 Masterclass ...

  9. SAP变式配置的完整指南(中英文双语版)

    A Complete Guide of SAP Variant Configuration SAP变式配置的完整指南 Variant configuration is for manufacturin ...

最新文章

  1. OpenStack潜力巨大:红帽打造生态系统
  2. 【Flutter】Dart 面向对象 ( get 方法 | set 方法 | 静态方法 )
  3. GDCM:检测SIEMENS JPEG无损压缩图像的测试程序
  4. scala list 接受java string_「软帝学院」Java零基础学习详解
  5. The SetStack Computer
  6. 26.python常用端口号
  7. android Studio 配置LUA 开发环境
  8. Linux服务-DHCP服务部署
  9. 将旧项目从Ant迁移到Maven的4个简单步骤
  10. 可是笑声太响了的64391111
  11. java自动类型转换与强制类型转换
  12. e0266 cout 不明确_荐书 | 不正义的时代,识别不正义的多重面孔
  13. flashBuilder安装Subclipse与XMLBuddy插件
  14. opencv 训练人脸对比_Page21-树莓派4B人脸检测与识别(opencv)
  15. idea的下载与安装破解
  16. 一键PDF转Word,PP-Structurev2文档分析模型深度解读!
  17. 详解会议中控系统及其优点特点有哪些?
  18. Android屏幕适配很难嘛?其实也就那么回事,吐血整理
  19. XYOj2113:找点(区间选点问题)
  20. 运放选型、参数分析以及应用

热门文章

  1. JavaScript代码在哪里放置?
  2. ubuntu14/ubuntu16/centos7:解决可以ping ip却ping不通主机名
  3. HACCP原理——确定关键控制点(转载)
  4. 计算机没考好的检讨书300百以上,考试反思检讨书300字(精选10篇)
  5. 特斯拉model3中控屏怎么关_玩转特斯拉Model 3:那些隐藏的功能和技巧
  6. 周师计算机专业学校分数线,周口师范学院是几本?录取分数线是多少
  7. 【会声会影】视频导出、输出时,如何设置参数
  8. python alphashape_Python alphashape包_程序模块 - PyPI - Python中文网
  9. 1.1到底什么是云计算
  10. Deblurring by Realistic Blurring 图像去模糊论文解读