IndexedDB 关键特性和基本术语
本文介绍了 IndexedDB 的主要特性,并引入了一些理解 IndexedDB API 所必需的基本术语。
您还会发现以下文章很有用
- 有关如何使用该 API 的详细教程,请参阅 使用 IndexedDB。
- 有关 IndexedDB API 的参考文档,请参阅主 IndexedDB API 文章及其子页面,其中记录了 IndexedDB 使用的对象类型。
- 有关浏览器如何在后台处理数据存储的更多信息,请阅读 浏览器存储配额和逐出标准。
主要特性
IndexedDB 是一种在用户浏览器中持久存储数据的方式。因为它允许您创建具有丰富查询功能的 Web 应用程序,而无论网络是否可用,这些应用程序既可以在线工作也可以离线工作。IndexedDB 对于存储大量数据(例如,借阅图书馆的 DVD 目录)的应用程序以及不需要持久互联网连接即可工作的应用程序(例如,邮件客户端、待办事项列表和记事本)非常有用。
IndexedDB 允许您存储和检索使用“键”索引的对象。您对数据库所做的所有更改都发生在事务中。像大多数 Web 存储解决方案一样,IndexedDB 遵循同源策略。因此,您可以在一个域内访问存储的数据,但不能跨不同域访问数据。
如果您对使用其他类型数据库有先入之见,那么在使用 IndexedDB 时可能会感到困惑。因此,以下 IndexedDB 的关键特性需要牢记:
-
IndexedDB 数据库存储键值对。值可以是复杂的结构化对象,键可以是这些对象的属性。您可以创建使用对象任何属性的索引,以便快速搜索和排序枚举。键可以是二进制对象。
-
IndexedDB 基于事务数据库模型构建。您在 IndexedDB 中所做的一切都始终在事务的上下文中发生。IndexedDB API 提供了许多表示索引、表、游标等的对象,但这些对象都绑定到特定的事务。因此,您不能在事务之外执行命令或打开游标。事务具有明确定义的生命周期,因此在事务完成后尝试使用它会抛出异常。此外,如果在事务处于活动状态时没有发出新的请求,事务会自动提交。
当您考虑如果用户同时在两个不同的选项卡中打开您的 Web 应用程序的两个实例时可能发生的情况,这种事务模型非常有用。如果没有事务操作,这两个实例可能会相互干扰修改。如果您不熟悉数据库中的事务,请阅读维基百科关于事务的文章。另请参阅定义部分中的事务。
-
IndexedDB API 大部分是异步的。该 API 不通过返回值来提供数据;相反,您必须传递一个回调函数。您不能通过同步方式将值“存储”到数据库中,也不能从数据库中“检索”值。相反,您“请求”数据库操作发生。当操作完成时,您会收到 DOM 事件通知,您收到的事件类型会告诉您操作成功还是失败。
-
IndexedDB 使用大量请求。请求是接收前面提到的成功或失败 DOM 事件的对象。它们具有
onsuccess和onerror属性,您可以在它们上调用addEventListener()和removeEventListener()。它们还具有readyState、result和errorCode属性,用于告诉您请求的状态。result属性尤其神奇,因为它可以是许多不同的东西,具体取决于请求是如何生成的(例如,一个IDBCursor实例,或者您刚刚插入到数据库中的值的键)。 -
IndexedDB 使用 DOM 事件通知您何时结果可用。DOM 事件总是有一个
type属性(在 IndexedDB 中,它最常设置为"success"或"error")。DOM 事件也有一个target属性,指示事件的目标。在大多数情况下,事件的target是由于执行某些数据库操作而生成的IDBRequest对象。成功事件不会冒泡,也不能取消。另一方面,错误事件会冒泡,并且可以取消。这非常重要,因为错误事件会中止它们正在运行的任何事务,除非它们被取消。 -
IndexedDB 是面向对象的。IndexedDB 不是一个关系型数据库,其中表表示行和列的集合。这种重要而根本的区别会影响您设计和构建应用程序的方式。
在传统的关系数据存储中,您会有一个表,其中存储着行数据集合和命名数据类型的列。而 IndexedDB 则要求您为一种数据类型创建一个对象存储,并将 JavaScript 对象持久化到该存储中。每个对象存储可以拥有一组索引,这些索引使得查询和迭代效率更高。如果您不熟悉面向对象数据库管理系统,请阅读维基百科上关于对象数据库的文章。
-
IndexedDB 不使用结构化查询语言 (SQL)。它使用对索引的查询来生成游标,您可以使用该游标遍历结果集。如果您不熟悉 NoSQL 系统,请阅读维基百科上关于 NoSQL 的文章。
-
IndexedDB 遵循同源策略。源是执行脚本的文档 URL 的域、应用层协议和端口。每个源都有其自己关联的数据库集。每个数据库都有一个名称,用于在源中标识它。
IndexedDB 施加的安全边界阻止应用程序访问不同源的数据。例如,
http://www.example.com/app/中的应用程序或页面可以从http://www.example.com/dir/检索数据,因为它们具有相同的源,但它不能从http://www.example.com:8080/dir/(不同端口)或https://www.example.com/dir/(不同协议)检索数据,因为它们具有不同的源。注意:第三方窗口内容(例如,
内容)可以访问其嵌入的源的 IndexedDB 存储,除非浏览器设置为永不接受第三方 cookie(参见Firefox bug 1147821)。
局限性
IndexedDB 旨在覆盖大多数需要客户端存储的情况。但是,它并非为以下几种情况而设计:
- 国际化排序。并非所有语言都以相同的方式对字符串进行排序,因此不支持国际化排序。虽然数据库无法以特定的国际化顺序存储数据,但您可以自己对从数据库中读取的数据进行排序。
- 同步。该 API 未设计用于与服务器端数据库同步。您必须编写代码来同步客户端 IndexedDB 数据库与服务器端数据库。
- 全文搜索。该 API 没有 SQL 中
LIKE运算符的等效项。
此外,请注意,浏览器可能会在以下情况下清除数据库:
- 用户请求清除。许多浏览器都有设置,允许用户清除给定网站存储的所有数据,包括 cookie、书签、存储的密码和 IndexedDB 数据。
- 浏览器处于私密浏览模式。一些浏览器有“私密浏览”(Firefox)或“隐身”(Chrome)模式。在会话结束时,浏览器会清除数据库。
- 磁盘或配额限制已达到。
- 数据已损坏。
- 对功能进行了不兼容的更改。
具体情况和浏览器功能会随着时间而变化,但浏览器供应商的普遍理念是尽可能地努力保留数据。
核心术语
本节定义并解释了理解 IndexedDB API 的核心术语。
数据库
数据库
信息的存储库,通常包含一个或多个对象存储。每个数据库必须具备以下特点:
- 名称。用于在特定源中标识数据库,并在其生命周期内保持不变。名称可以是任何字符串值(包括空字符串)。
- 当前版本。数据库首次创建时,如果未另行指定,其版本为整数 1。每个数据库在任何给定时间只能有一个版本。
数据库连接
通过打开数据库创建的操作。一个给定的数据库可以同时有多个连接。
持久性
在 Firefox 中,IndexedDB 曾经是持久的,这意味着在读写事务中,complete 事件仅在所有数据都保证已刷新到磁盘时才触发。
自 Firefox 40 起,IndexedDB 事务放宽了持久性保证以提高性能(参见Firefox bug 1112702),这与其他支持 IndexedDB 的浏览器行为相同。在这种情况下,complete 事件在操作系统被告知写入数据之后但在数据实际刷新到磁盘之前可能触发。因此,该事件可能会比以前更快地传递,但是,如果操作系统崩溃或在数据刷新到磁盘之前系统断电,则整个事务可能会丢失。由于此类灾难性事件很少见,大多数用户无需进一步关注。
注意:在 Firefox 中,如果您出于某种原因希望确保持久性(例如,您正在存储无法稍后重新计算的关键数据),您可以通过使用实验性(非标准)readwriteflush 模式创建事务,强制事务在传递 complete 事件之前刷新到磁盘(参见IDBDatabase.transaction)。这目前是实验性的,并且只有在 about:config 中将 dom.indexedDB.experimental 首选项设置为 true 时才能使用。
index
索引是一个专门的对象存储,用于在另一个名为引用对象存储的对象存储中查找记录。索引是持久的键值存储,其记录的值部分是引用对象存储中记录的键部分。索引中的记录在引用对象存储中的记录被插入、更新或删除时会自动填充。索引中的每条记录只能指向其引用对象存储中的一条记录,但多个索引可以引用同一个对象存储。当对象存储发生变化时,所有引用该对象存储的索引都会自动更新。
或者,您也可以使用键在对象存储中查找记录。
要了解更多关于使用索引的信息,请参阅使用 IndexedDB。有关索引的参考文档,请参阅IDBKeyRange。
对象存储
数据在数据库中存储的机制。对象存储持久地保存记录,记录是键值对。对象存储中的记录根据键按升序排序。
每个对象存储必须有一个在其数据库中唯一的名称。对象存储可以选择具有一个键生成器和一个键路径。如果对象存储具有键路径,则它使用内联键;否则,它使用非内联键。
有关对象存储的参考文档,请参阅IDBObjectStore。
请求
对数据库进行读写操作。每个请求代表一个读或写操作。
事务
对特定数据库进行数据访问和数据修改操作的原子集合。它是您与数据库中的数据交互的方式。事实上,数据库中任何数据的读取或更改都必须在事务中进行。
一个数据库连接可以同时关联多个活动事务,只要写入事务没有重叠的范围。事务的范围在创建时定义,它决定了事务可以与哪些对象存储交互,并在事务的生命周期内保持不变。因此,例如,如果一个数据库连接已经有一个范围只覆盖 flyingMonkey 对象存储的写入事务,您可以启动第二个事务,其范围为 unicornCentaur 和 unicornPegasus 对象存储。至于读取事务,您可以有多个——甚至可以有重叠的。
事务预期是短命的,因此浏览器可能会终止耗时过长的事务,以释放长时间运行事务锁定的存储资源。您可以中止事务,这将回滚在事务中对数据库所做的更改。您甚至不需要等待事务开始或激活即可中止它。
事务有三种模式:readwrite、readonly 和 versionchange。创建和删除对象存储和索引的唯一方法是使用versionchange事务。要了解更多关于事务类型的信息,请参阅IndexedDB的参考文章。
由于一切都发生在事务中,因此它是 IndexedDB 中一个非常重要的概念。要了解有关事务的更多信息,尤其是它们与版本控制的关系,请参阅IDBTransaction,其中也包含参考文档。
版本
当数据库首次创建时,其版本为整数 1。每个数据库同时只有一个版本;一个数据库不能同时存在于多个版本中。更改版本的唯一方法是使用比当前版本更高的版本打开它。
键和值
内联键
作为存储值的一部分存储的键。它通过键路径找到。内联键可以使用生成器生成。键生成后,可以使用键路径将其存储在值中,也可以将其用作键。
key
用于在对象存储中组织和检索存储值的数据值。对象存储可以从三个来源之一派生键:键生成器、键路径或显式指定的值。键的数据类型必须是数字大于前一个数字的数据类型。对象存储中的每个记录都必须有一个在该存储中唯一的键,因此在给定的对象存储中不能有多个具有相同键的记录。
键可以是以下类型之一:字符串、日期、浮点数、二进制大对象和数组。对于数组,键的范围可以从空值到无穷大。您可以在数组中包含数组。
或者,您也可以使用索引在对象存储中查找记录。
键生成器
一种按有序序列生成新键的机制。如果对象存储没有键生成器,则应用程序必须为要存储的记录提供键。生成器在存储之间不共享。这更像是一个浏览器实现细节,因为在 Web 开发中,您实际上不会创建或访问键生成器。
键路径
定义浏览器应该从对象存储或索引中提取键的位置。有效的键路径可以包含以下之一:空字符串、JavaScript 标识符、由句点分隔的多个 JavaScript 标识符,或包含其中任何一个的数组。它不能包含空格。
非内联键
与被存储的值分开存储的键。
value
每条记录都有一个值,其中可以包含任何可以用 JavaScript 表达的内容,包括布尔值、数字、字符串、日期、对象、数组、正则表达式、undefined 和 null。
当存储一个对象或数组时,该对象或数组中的属性和值也可以是任何有效值。
二进制大对象 (Blob) 和文件可以存储,请参阅规范。
范围和作用域
cursor
一种使用键范围迭代多个记录的机制。游标有一个源,指示它正在迭代哪个索引或对象存储。它在范围内有一个位置,并按照记录键的顺序向递增或递减方向移动。有关游标的参考文档,请参阅IDBCursor。
键范围
用于键的某种数据类型上的连续区间。可以使用键或键范围从对象存储和索引中检索记录。您可以使用下限和上限来限制或过滤范围。例如,您可以迭代键在 x 和 y 之间的所有值。
有关键范围的参考文档,请参阅IDBKeyRange。
scope
事务应用于的对象存储和索引的集合。只读事务的范围可以重叠并同时执行。另一方面,写入事务的范围不能重叠。您仍然可以同时启动具有相同范围的多个事务,但它们只是排队并一个接一个地执行。
后续步骤
了解了 IndexedDB 的主要特性和核心术语之后,我们可以开始更具体的内容。有关如何使用该 API 的教程,请参阅使用 IndexedDB。