这是索引数据库 API 的第三版。 第一版, 标题为“Indexed Database API”, 于 2015 年 1 月 8 日成为 W3C 推荐标准。 第二版, 标题为“Indexed Database API 2.0”, 于 2018 年 1 月 30 日成为 W3C 推荐标准。
Indexed Database API 3.0 旨在取代 Indexed Database API 2.0。
1. 简介
用户代理需要在本地存储大量对象,以满足 Web 应用的离线数据需求。[WEBSTORAGE] 可用于存储键值对。 然而,它不支持按顺序检索键、对值高效搜索,或为同一键存储重复值。
本规范提供了一个具体的 API,用于执行高级键值数据管理,这是大多数复杂查询处理器的核心。它通过事务性数据库存储键及其对应值(每个键可对应一个或多个值),并提供按照确定性顺序遍历键的方法。通常通过持久化 B-树数据结构实现,这在插入、删除以及对大量数据记录进行有序遍历时都非常高效。
"library"
的数据库。它有一个
"books"
对象存储区,按 "isbn"
属性作为主键存储图书记录。
图书记录有一个 "title"
属性。此示例人为要求书名必须唯一。代码通过创建名为 "by_title"
的索引,并设置 unique
选项来强制唯一性。该索引用于按标题查找图书,并防止添加重复书名。
图书记录还有一个 "author"
属性,并不要求唯一。代码创建了另一个名为
"by_author"
的索引,以便通过该属性查找。
代码首先打开数据库连接。upgradeneeded
事件处理代码在需要时创建对象存储区和索引。success
事件处理代码用于保存打开的连接,以便后续示例使用。
const request= indexedDB. open( "library" ); let db; request. onupgradeneeded= function () { // 数据库之前不存在,因此创建对象存储区和索引。 const db= request. result; const store= db. createObjectStore( "books" , { keyPath: "isbn" }); const titleIndex= store. createIndex( "by_title" , "title" , { unique: true }); const authorIndex= store. createIndex( "by_author" , "author" ); // 填充初始数据。 store. put({ title: "Quarry Memories" , author: "Fred" , isbn: 123456 }); store. put({ title: "Water Buffaloes" , author: "Fred" , isbn: 234567 }); store. put({ title: "Bedrock Nights" , author: "Barney" , isbn: 345678 }); }; request. onsuccess= function () { db= request. result; };
下面的示例通过事务填充数据库。
const tx= db. transaction( "books" , "readwrite" ); const store= tx. objectStore( "books" ); store. put({ title: "Quarry Memories" , author: "Fred" , isbn: 123456 }); store. put({ title: "Water Buffaloes" , author: "Fred" , isbn: 234567 }); store. put({ title: "Bedrock Nights" , author: "Barney" , isbn: 345678 }); tx. oncomplete= function () { // 所有请求均已成功,事务已提交。 };
下面的示例通过标题使用索引查找单本书籍。
const tx= db. transaction( "books" , "readonly" ); const store= tx. objectStore( "books" ); const index= store. index( "by_title" ); const request= index. get( "Bedrock Nights" ); request. onsuccess= function () { const matching= request. result; if ( matching!== undefined ) { // 找到匹配项。 report( matching. isbn, matching. title, matching. author); } else { // 未找到匹配项。 report( null ); } };
下面的示例通过作者使用索引和游标查找所有书籍。
const tx= db. transaction( "books" , "readonly" ); const store= tx. objectStore( "books" ); const index= store. index( "by_author" ); const request= index. openCursor( IDBKeyRange. only( "Fred" )); request. onsuccess= function () { const cursor= request. result; if ( cursor) { // 针对每个匹配记录调用。 report( cursor. value. isbn, cursor. value. title, cursor. value. author); cursor. continue (); } else { // 没有更多匹配记录。 report( null ); } };
下面的示例展示了请求失败时处理错误的方法之一。
const tx= db. transaction( "books" , "readwrite" ); const store= tx. objectStore( "books" ); const request= store. put({ title: "Water Buffaloes" , author: "Slate" , isbn: 987654 }); request. onerror= function ( event) { // "by_title" 索引的唯一性约束失败。 report( request. error); // 可以调用 event.preventDefault() 以防止事务自动中止。 }; tx. onabort= function () { // 否则,事务会因请求失败自动中止。 report( tx. error); };
当数据库连接不再需要时,可以关闭。
db. close();
将来,数据库可能已扩展为包含其他对象存储区和索引。下面的示例展示了如何从旧版本迁移。
const request= indexedDB. open( "library" , 3 ); // 请求第 3 版。 let db; request. onupgradeneeded= function ( event) { const db= request. result; if ( event. oldVersion< 1 ) { // 第 1 版为数据库的首个版本。 const store= db. createObjectStore( "books" , { keyPath: "isbn" }); const titleIndex= store. createIndex( "by_title" , "title" , { unique: true }); const authorIndex= store. createIndex( "by_author" , "author" ); } if ( event. oldVersion< 2 ) { // 第 2 版新增了按年份索引图书。 const bookStore= request. transaction. objectStore( "books" ); const yearIndex= bookStore. createIndex( "by_year" , "year" ); } if ( event. oldVersion< 3 ) { // 第 3 版新增了用于杂志的新对象存储区及两个索引。 const magazines= db. createObjectStore( "magazines" ); const publisherIndex= magazines. createIndex( "by_publisher" , "publisher" ); const frequencyIndex= magazines. createIndex( "by_frequency" , "frequency" ); } }; request. onsuccess= function () { db= request. result; // db.version 将会是 3。 };
upgradeneeded
事件),只有在所有其他客户端关闭了对当前版本数据库的连接后,才能进行升级。
为避免阻塞新客户端升级,客户端可以监听
versionchange
事件。当其他客户端希望升级数据库时会触发该事件。为允许升级继续进行,可以在收到 versionchange
事件时采取措施,最终关闭该客户端与数据库的连接。
一种做法是重新加载页面:
db. onversionchange= function () { // 首先,保存所有未保存的数据: saveUnsavedData(). then( function () { // 如果文档未被主动使用,可以在无需用户交互的情况下重新加载页面。 if ( ! document. hasFocus()) { location. reload(); // 页面重载将关闭数据库,也会加载新的 JavaScript 和数据库定义。 } else { // 如果文档有焦点,强制重载页面可能会太扰乱用户。 // 可以提示用户手动刷新: displayMessage( "请刷新页面以获取最新版本。" ); } }); }; function saveUnsavedData() { // 如何保存取决于你的应用。 } function displayMessage() { // 给用户展示非模态消息。 }
另一种方法是调用连接的 close()
方法。但需要确保你的应用对此有感知,因为后续访问数据库的尝试会失败。
db. onversionchange= function () { saveUnsavedData(). then( function () { db. close(); stopUsingTheDatabase(); }); }; function stopUsingTheDatabase() { // 让应用进入不再使用数据库的状态。 }
新客户端(尝试升级的那个)可以通过 blocked
事件,
检测其他客户端是否阻止升级。blocked
事件在其他客户端的 versionchange
事件已触发但仍未断开连接时触发。
const request= indexedDB. open( "library" , 4 ); // 请求第 4 版。 let blockedTimeout; request. onblocked= function () { // 给其他客户端异步保存数据的时间。 blockedTimeout= setTimeout( function () { displayMessage( "升级被阻止 - 请关闭其他正在显示本站的标签页。" ); }, 1000 ); }; request. onupgradeneeded= function ( event) { clearTimeout( blockedTimeout); hideMessage(); // ... }; function hideMessage() { // 隐藏之前显示的消息。 }
只有当其他客户端未能断开数据库连接时,用户才会看到上述消息。理想情况下,用户永远不会看到此消息。
2. 结构
名称是一个字符串,等同于DOMString
;
即任意长度的 16 位码元序列,包括空字符串。名称始终作为不透明的 16 位码元序列进行比较。
如果实现使用的存储机制不支持任意字符串,则可采用转义机制或类似方法将提供的名称映射为可存储的字符串。
要从 列表names创建排序名称列表,执行以下步骤:
-
返回一个与 sorted 关联的新
DOMStringList
。
详情
这与Array.prototype.sort()
方法对 Array
中的 String
的行为一致。
此排序按字符串的 16 位码元比较,保证高效、一致且确定性的顺序。结果列表不会匹配任何特定字母表或字典序,尤其是由代理对组成的码点。
2.1. 数据库
每个存储键都关联一组数据库。数据库有零个或多个对象存储区,用于保存数据库中的数据。
一个数据库有一个名称,用于在特定存储键内标识它。名称为名称类型,并在数据库生命周期内保持不变。
一个数据库有一个版本。数据库首次创建时,版本 为 0(零)。
2.1.1. 数据库连接
脚本不直接与数据库交互。脚本通过连接间接访问数据库。 连接对象可用于操作该数据库中的对象,也是获取该数据库 事务的唯一途径。
打开数据库会创建一个连接。 任一时刻,一个数据库可能有多个连接。
连接有一个版本,在连接创建时设定。其值在连接 存续期内保持不变,除非升级被中止,此时设置为数据库的先前版本。一旦连接关闭,版本不会再变化。
每个连接有一个待关闭标志,初始为 false。
连接初建时为打开状态。连接可通过多种方式关闭。 若创建连接的执行上下文被销毁(如用户离开该页面),连接会关闭。也可使用关闭数据库连接步骤主动关闭。关闭后,待关闭标志会被设置为 true(若尚未设置)。
在异常情况下,如文件系统不可访问、权限变更或清除存储键的存储时,用户代理可关闭连接。此时必须用强制标志为 true 执行关闭数据库连接步骤。
连接有一个对象存储区集合,在创建连接时初始化为关联数据库中的对象存储区集合。除非有升级事务处于活动状态,集合内容保持不变。
连接的get the parent算法返回 null。
若试图升级或删除数据库,会在打开的连接上触发类型为versionchange
的事件。这样连接可选择关闭,以允许升级或删除继续进行。
若连接异常关闭,会在连接上触发类型为close
的事件。
2.2. 对象存储区
对象存储区是数据库中数据的主要存储机制。
每个数据库有一组对象存储区。对象存储区的集合只能通过升级事务进行更改,
即响应upgradeneeded
事件。当新数据库创建时,不包含任何对象存储区。
对象存储区有一个记录列表,用于保存对象存储区中的数据。每个记录由一个键和一个值组成。列表按键升序排序。对象存储区中不能有重复键的记录。
对象存储区有一个名称,是一个名称类型。 在所属数据库内,任一时刻名称都是唯一的。
对象存储区可选有键路径。有键路径时称为使用内联键;否则称为使用外部键。
2.2.1. 对象存储区句柄
脚本不直接与对象存储区交互。而是在事务中,脚本通过对象存储区句柄间接访问。
对象存储区句柄关联一个对象存储区 和一个事务。不同事务可关联同一个对象存储区的多个句柄, 但在同一事务内,某个对象存储区只能有一个对象存储区句柄。
一个对象存储句柄有一个索引集,在创建对象存储句柄时,该索引集被初始化为引用关联对象存储的索引集合。该集合的内容将保持不变,除非有升级事务处于激活状态。
对象存储句柄拥有一个名称,在创建对象存储句柄时,该名称被初始化为关联对象存储的名称。该名称将保持不变,除非有升级事务处于激活状态。
2.3. 值
每条记录都关联一个值。用户代理必须支持任意可序列化对象。这包括简单类型,如String
原始值和Date
对象,以及
Object
和Array
实例,File
对象、Blob
对象、ImageData
对象等。记录的值是按值存储和检索的,而不是按引用;后续对值的修改不会影响数据库中已存储的记录。
记录的值是Records,由StructuredSerializeForStorage操作输出。
2.4. 键
为了高效检索存储在索引数据库中的记录,每条记录都按其键组织。
键有一个关联的类型,可为: number(数字), date(日期), string(字符串), binary(二进制), 或 array(数组)。
键还关联一个值,
其可能为:
当类型为number或date时,为unrestricted double
;
当类型为string时,为DOMString
;
当类型为binary时,为字节序列;
当类型为array时,为其它键组成的列表。
ECMAScript [ECMA-262]值可按将值转换为键步骤转换为键。
-
Number
原始值(除 NaN 外),包括 Infinity 和 -Infinity。 -
Date
对象(除 [[DateValue]] 内部槽为 NaN 的情况)。 -
String
原始值。 -
ArrayBuffer
对象(或缓冲区视图,如Uint8Array
)。 -
Array
对象,前提是每项已定义,且本身为有效键,且不会直接或间接包含自身。包括空数组。数组可以包含其它数组。
尝试将其它 ECMAScript 值转换为键 会失败。
数组键是键,其类型为array。 子键指数组键的项, 即数组键的值。
要比较两个键a和b,执行如下步骤:
注意: 按上述规则,负无穷是键的最小可能值。 number键小于date键。 date键小于string键。 string键小于binary键。 binary键小于array键。 没有最大可能键值。 因为任何候选最大键的数组,再加一个键,会更大。
注意:
binary键的成员按无符号字节值(0 到 255)比较,
而不是按带符号byte
值(-128 到 127)。
2.5. 键路径
键路径是一个字符串或字符串列表, 用于定义如何从值中提取键。有效的键路径包括:
-
空字符串。
-
标识符,即与 ECMAScript 语言规范的IdentifierName产生式匹配的字符串 [ECMA-262]。
-
由两个或多个标识符以点(U+002E FULL STOP)分隔组成的字符串。
-
只包含符合上述要求字符串的非空字符串列表。
注意: 键路径中不允许有空格。
键路径值只能通过StructuredSerializeForStorage显式复制的属性访问, 以及下列类型专属属性:
类型 | 属性 |
---|---|
Blob
| size ,
type
|
File
| name ,
lastModified
|
Array
| length
|
String
| length
|
2.6. 索引
有时除了通过键外,也需要通过其他方式检索记录。索引允许通过对象存储区中记录的值的属性查找记录。
索引是一种专门的持久化键值存储,并拥有一个被引用对象存储。 索引有一个记录列表,用于保存存储在索引中的数据。 索引中的记录会在 被引用对象存储插入、更新或删除记录时自动填充。 可以有多个索引引用同一个对象存储,此时对对象存储的更改会使所有这些索引都得到更新。
索引中值总是索引引用的对象存储区中的键值。键由被引用对象存储区的值通过键路径派生。 如果索引引用的对象存储区中,键为X的记录值为A,索引的键路径在A上计算结果为Y, 则索引中会有一条键为Y、值为X的记录。
123
、值为{ name: "Alice", title: "CEO" }
的记录,
且索引的键路径为"name
",
那么索引中会有一条键为"Alice
"、值为123
的记录。
索引中的记录有被引用值, 即索引引用的对象存储区中键等于索引记录值的那条记录的值。在上述例子中,索引中键为Y、值为X的记录的被引用值为A。
注意: 索引中的每条记录只引用索引的被引用对象存储区的一条记录。 但一个索引中可以有多条记录引用对象存储区的同一条记录,也可能没有任何记录引用对象存储区的某条记录。
索引中的记录总是按记录的键排序。 与对象存储区不同,一个索引可包含多个键相同的记录。这些记录会再根据索引记录的值 (即被引用对象存储区记录的键)排序。
索引有一个名称,为名称类型。 在索引的被引用对象存储区内,名称始终唯一。
索引有唯一性标志。为 true 时,索引会强制索引中没有两条记录具有相同的键。如果尝试在索引引用的对象存储区插入或修改某条记录, 使得索引的键路径计算结果已在索引中存在,则对象存储区的插入或修改操作会失败。
索引有一个multiEntry 标志。该标志会影响当索引的键路径的求值结果为数组键时的行为。如果multiEntry 标志为 false,则会向索引中添加一个记录,其键为数组键。如果multiEntry 标志为 true,则会为每个子键向索引中添加一个记录。
2.6.1. 索引句柄
脚本不直接与索引交互。而是在事务中, 通过索引句柄间接访问索引。
2.7. 事务
事务用于与数据库中的数据交互。所有数据的读写都必须通过事务完成。
事务可为应用和系统故障提供保护。事务可用于存储多条数据记录或有条件地修改某些数据记录。事务表示一组原子且持久的数据访问和数据变更操作。
所有事务都通过一个连接创建,该连接是事务的连接。
若两个事务的作用域有任意对象存储区重叠,则称这两个事务作用域重叠(overlapping scope)。
事务有一个模式,决定该事务可执行的操作类型。模式在事务创建时设定,并在事务生命周期内保持不变。事务的模式可为:
- "
readonly
" -
只允许读取数据,不能进行修改。优点是多个只读事务可同时启动, 即使他们的作用域有重叠(使用相同对象存储区)。只要数据库已打开,随时可创建此类事务。
- "
readwrite
" -
允许读取、修改和删除现有对象存储区的数据,但不能添加或移除对象存储区和索引。若多个"
readwrite
" 事务的作用域 有重叠,则不能同时启动,因为可能会在事务进行期间互相修改数据。只要数据库已打开,随时可创建此类事务。 - "
versionchange
" -
允许读取、修改和删除现有对象存储区的数据,也可以创建和移除对象存储区及索引。只有此类事务可以进行这些操作。该事务不能手动创建,而是在触发
upgradeneeded
事件时自动创建。
事务有一个持久化提示。此提示用于告知用户代理在提交事务时应优先考虑性能还是持久性。持久化提示可为:
strict
"
是对用户代理的提示,在触发complete
事件前应刷新操作系统 I/O 缓冲区。这样可提高数据在操作系统崩溃或断电情况下持久保存的概率,但刷新缓冲区可能耗时且消耗便携设备的电池寿命。
建议 Web 应用对临时数据(如缓存或快速变化记录)使用"relaxed
",
对于需降低数据丢失风险的场景使用"strict
"。
实现应根据应用的持久化提示权衡对用户和设备的影响。
abort()
设置的。
事务的get the parent 算法返回事务的连接。
2.7.1. 事务生命周期
事务有一个状态,可为以下几种:
- 活动
-
事务初次创建时,以及在分发与该事务关联的请求事件期间,处于此状态。
当事务处于此状态时,可以发起新的请求。
- 非活动
-
事务创建后,控制返回事件循环或事件未分发时,处于此状态。
当事务处于此状态时,不能发起请求。
- 提交中
-
当事务处于此状态时,不能发起请求。
- 已结束
-
事务成功提交或中止后,进入此状态。
当事务处于此状态时,不能发起请求。
事务应短暂存续。自动提交机制(见下文)鼓励这一点。
注意: 作者仍可让事务长时间存活; 但这种用法不建议,因为会影响用户体验。
生命周期如下:
-
当实现能够强制事务的作用域和模式约束(见下文),应排队数据库任务,异步启动事务。
事务启动后,实施可开始执行针对该事务发起的请求。请求必须按发起顺序依次执行,结果也要按请求顺序返回。不同事务的请求结果返回顺序不保证。
注意: 事务模式确保不同事务的请求可任意顺序执行,且不会影响数据库存储的数据。
-
当事务的每个请求处理后,会触发
success
或error
事件。事件分发期间,事务状态设为活动,允许新增请求。事件分发完成后,事务状态设回非活动。 -
显式调用
abort()
会发起中止。脚本未处理的请求失败也会导致中止。中止事务时,实施必须回滚事务期间对数据库做的所有变更,包括对象存储区内容及对象存储区、索引的添加与移除。
-
当事务所有请求完成且结果已处理,且未被中止,实施应尝试提交一个非活动事务。
显式调用
commit()
可立即发起提交,无需等待请求结果由脚本处理。提交时,事务状态设为提交中。实施必须原子性地写入事务请求产生的所有数据库变更。要么全部写入,要么遇到错误(如磁盘写入失败)则不写入任何变更,并按中止事务步骤处理。
实施必须允许在事务处于活动状态时发起请求。即使事务尚未启动。在事务启动前,实施不得执行这些请求,但必须记录请求及其顺序。
要清理索引数据库事务,执行如下步骤。如果有事务被清理则返回 true,否则返回 false。
注意:
这些步骤由[HTML]调用。确保通过脚本调用transaction()
创建的事务在任务完成后被停用。每个事务最多运行一次这些步骤。
2.7.2. 事务调度
实现可能还会施加额外约束。例如,实现无需并行启动非作用域重叠的读写事务,或可能对已启动的事务数量设限。
-
只要只读事务存活, 实现通过该事务创建的请求返回的数据保持不变。即,两次读取同一数据的请求,无论数据是否存在,结果都一样。
-
读写事务只受自身对对象存储区的更改影响。实现确保其他事务不会修改该读写事务作用域内的对象存储区内容。同时,保证若该读写事务成功完成,其变更可无冲突地提交到数据库。
-
若多个读写事务尝试访问同一个对象存储区(即作用域重叠),最先创建的事务优先获得访问权,且在事务结束前,只有它能访问该对象存储区。
-
在某个读写事务之后创建的任意事务都能看到该读写事务写入的变更。例如,读写事务A创建后,随后又创建事务B,且两者有作用域重叠,则事务B可以看到A在重叠作用域内对象存储区所做的任何更改。但直到A结束前,B无法访问这些对象存储区。
2.7.3. 升级事务
升级事务是事务,其模式
为"versionchange
"。
当打开连接到数据库,指定的版本大于当前版本时,运行升级数据库步骤会自动创建升级事务。该事务会在upgradeneeded
事件处理器内处于活动状态。
升级事务是排他性的。打开数据库连接步骤确保升级事务存活期间,数据库只有一个连接处于打开状态。
只有在所有其他连接关闭后,upgradeneeded
事件才会被触发,升级事务才会启动。这样可确保所有之前的事务均已结束。
只要升级事务存活,对同一数据库打开更多连接的尝试会被延迟,且对同一连接调用transaction()
启动新事务会抛出异常。这样可以确保没有其他事务同时存活,也确保在升级事务存活期间不会对同一数据库排队新事务。
2.8. 请求
对数据库的每个异步操作都通过请求完成。每个请求代表一个操作。
请求有一个已处理标志,初始值为 false。当与请求关联的操作被执行后,该标志会被设置为 true。
请求有一个完成标志,初始值为 false。当与请求关联的操作结果可用时,该标志会被设置为 true。
请求有一个源对象。
请求有一个结果和一个错误,在完成标志为 true 之前都无法访问。
请求有一个事务,初始值为 null。当请求根据异步执行请求步骤被放置到某个事务时会被设置。
当一个请求被发起时,会返回一个新的请求,其完成标志为 false。如果请求成功完成,其完成标志会被设置为 true,结果会被设置为请求的结果,并且会在请求上触发类型为success
的事件。
如果在执行操作时发生错误,请求的完成标志会被设置为
true,错误会被设置为该错误,并且会在请求上触发类型为error
的事件。
注意:
请求通常不会被重复使用,但也有例外。例如,当一个游标被迭代时,迭代结果会在用于打开游标的同一个请求对象上报告。当需要升级事务时,打开请求会同时用于upgradeneeded
事件和最终打开操作的结果。在某些情况下,请求的完成标志会先被设置为 false,再被设置为
true,且结果可能会发生变化或者错误会被设置。
2.8.1. 打开请求
打开请求是一种特殊类型的请求,用于打开连接或删除数据库。除了success
和error
事件外,还可能会在打开请求上触发blocked
和upgradeneeded
事件以指示进度。
打开请求的事务为 null,除非已触发upgradeneeded
事件。
2.8.2. 连接队列
打开请求会在连接队列中处理。 队列包含所有与某个存储键和名称关联的打开请求。队列中的请求按顺序处理,每个请求必须运行至完成,才能处理下一个请求。打开请求可能因其他连接阻塞,需要这些连接关闭后,请求才能完成并允许后续请求处理。
注意: 连接队列不是与任务队列相关联的事件循环,因为请求是在任何特定浏览上下文之外处理的。对于已完成的打开请求,事件的派发仍然会通过与请求发起时所在上下文的事件循环相关联的任务队列进行。
2.9. 键区间
可以通过键或键区间从对象存储区和索引检索记录。键区间是用于键的数据类型上的连续区间。
键区间仅包含key,即下界与上界都等于key。
当满足以下两个条件时,key在键区间内range:
无界键范围是键范围,其下界和上界都为 null。所有键都属于无界键范围。
要将值转换为键区间,参数为value和可选null disallowed flag,执行如下步骤:
-
如果value是键区间,返回value。
-
如果value为 undefined 或 null,若null disallowed flag为 true,则抛出"
DataError
"DOMException
,否则返回无界键区间。 -
令key为用value转换为键的结果。任何异常均重新抛出。
-
若key为"invalid value"或"invalid type",抛出"
DataError
"DOMException
。 -
返回仅包含key的键区间。
潜在有效键区间是可转换为键区间类型的 ECMAScript 值。 至于具体值能否成功转换为键区间(即用其将值转换为键区间会不会抛异常),不做要求。
注意: 例如,分离的 BufferSource是潜在有效键区间,但用将值转换为键区间时会抛异常。
要判断 ECMAScript value是否为潜在有效键区间,执行如下步骤:
getAll()
和getAllKeys()
方法用是否为潜在有效键区间处理第一个参数。
若参数为潜在有效键区间,则getAll()
和getAllKeys()
用该参数执行将值转换为键区间。否则使用IDBGetAllOptions
作为第一个参数。
getAll()
和getAllKeys()
对第一个参数为Date
、
Array
、
ArrayBuffer
,且用转换为键返回"invalid value"时会抛异常。
例如,用 NaN Date
作为第一个参数调用getAll()
会抛异常,而不是用默认值执行IDBGetAllOptions
字典。
2.10. 游标
游标有一个记录区间,存在于索引或对象存储区中。
游标有一个源,它是游标的源句柄中的一个索引或对象存储。 游标的源表示游标正在遍历的记录所属的索引或对象存储。 如果游标的源句柄是索引句柄,那么游标的源就是该索引句柄关联的索引。 否则,游标的源就是该对象存储句柄关联的对象存储。
游标有一个方向,决定游标遍历记录时是按键单调递增还是递减,并决定遍历索引时是否跳过重复值。方向也决定游标初始位置在源的起点还是终点。游标的方向可为:
- "
next
" -
该方向使游标在源的起点打开。遍历时游标应以键值单调递增顺序返回所有记录(包括重复项)。
- "
nextunique
" -
该方向使游标在源的起点打开。遍历时游标不返回键值相同的记录,只返回每个键的第一条记录,其余记录按单调递增顺序返回。当源为对象存储区或唯一性标志为 true 的索引时,此方向行为与 "
next
" 完全一致。 - "
prev
" -
该方向使游标在源的终点打开。遍历时游标应以键值单调递减顺序返回所有记录(包括重复项)。
- "
prevunique
" -
该方向使游标在源的终点打开。遍历时游标不返回键值相同的记录,只返回每个键的第一条记录,其余记录按单调递减顺序返回。当源为对象存储区或唯一性标志为 true 的索引时,此方向行为与 "
prev
" 完全一致。
游标在其范围内有一个位置。游标正在遍历的记录列表在整个范围迭代完成前可能会发生变化。为了解决这个问题,游标并不是以索引来维护其位置,而是以上一次返回的记录的键来维护。对于正向遍历的游标,下次迭代时会返回键值比上一次返回的记录更大的最小记录。对于反向遍历的游标,则相反,会返回键值比上一次返回的记录更小的最大记录。
对于遍历索引的游标情况会稍复杂一些,因为多个记录可能具有相同的键,因此还会按值进行排序。遍历索引时,游标还拥有一个对象存储位置,用于表示索引中先前找到的记录的值。寻找下一个合适的记录时,会同时使用位置和对象存储位置。
游标有一个获取值标志。当该标志为 false 时,游标要么正在加载下一个值,要么已经到达其范围末尾。当它为 true 时,表示游标当前持有一个值,并且准备好迭代到下一个值。
如果游标的源是对象存储,那么游标的有效对象存储就是该对象存储,游标的有效键就是游标的位置。如果游标的源是索引,那么游标的有效对象存储就是该索引的被引用对象存储,有效键是游标的对象存储位置。
2.11. 键生成器
创建对象存储区时,可指定使用键生成器。键生成器用于为插入到对象存储区的记录生成键(若未显式指定)。
键生成器有一个当前数值。当前数值始终为小于等于 253 (9007199254740992) + 1 的正整数。键生成器的当前数值初始为 1,在关联对象存储区创建时设定。当前数值随生成键递增,也可通过显式键更新为特定值。
注意: 每个使用键生成器的对象存储区都有独立的生成器。操作某对象存储区不会影响其他对象存储区的键生成器。
修改键生成器的当前数值属于数据库操作。如果操作失败并被回滚,当前数值会恢复为操作前的值。无论是因键生成器使用而递增,还是因存储记录时使用显式键而修改,均适用此规则。
同样,如事务中止,事务作用域内每个对象存储区的键生成器当前数值会恢复为事务开始前的值。
键生成器的当前数值不会减少,除非数据库操作被回滚。删除记录不会影响对象存储区的键生成器。即使清空所有记录(如调用clear()
方法),也不会影响键生成器的当前数值。
要为对象存储区store生成键,执行:
要为对象存储区store,用key可能更新键生成器,执行:
只有类型为number的指定键才会影响键生成器的当前数值。类型为date、array(无论包含什么键)、binary或string(无论是否能被解析为数字)的键都不会影响键生成器的当前数值。类型为number且值小于 1 的键不会影响当前数值,因为它们始终小于键生成器的当前数值。
当键生成器的当前数值超过 253 (9007199254740992)
时,任何后续使用键生成器生成新键的尝试都会导致抛出"ConstraintError
"
DOMException
。但仍可通过显式指定键插入记录,要重新使用键生成器只能删除并新建对象存储区。
Number
无法唯一表示大于
9007199254740992 的整数。例如,9007199254740992 + 1 === 9007199254740992
在 ECMAScript 中成立。
只要正常使用键生成器,这个限制不会成为问题。如果你每天每秒生成 1000 个新键,也要 285000 年才会遇到上限。
实际结果是,对象存储区第一个生成的键总是 1(除非先插入更大的数值键),且生成的键总是比存储区中最大数值键大。除非事务回滚,同一个对象存储区不会生成重复键。
每个对象存储区都有自己的键生成器:
store1= db. createObjectStore( "store1" , { autoIncrement: true }); store1. put( "a" ); // 得到键 1 store2= db. createObjectStore( "store2" , { autoIncrement: true }); store2. put( "a" ); // 得到键 1 store1. put( "b" ); // 得到键 2 store2. put( "b" ); // 得到键 2
若插入因约束冲突或 IO 错误失败,键生成器不会更新。
transaction. onerror= function ( e) { e. preventDefault() }; store= db. createObjectStore( "store1" , { autoIncrement: true }); index= store. createIndex( "index1" , "ix" , { unique: true }); store. put({ ix: "a" }); // 得到键 1 store. put({ ix: "a" }); // 失败 store. put({ ix: "b" }); // 得到键 2
移除对象存储区的项不会影响键生成器,即使调用clear()
也不影响。
store= db. createObjectStore( "store1" , { autoIncrement: true }); store. put( "a" ); // 得到键 1 store. delete ( 1 ); store. put( "b" ); // 得到键 2 store. clear(); store. put( "c" ); // 得到键 3 store. delete ( IDBKeyRange. lowerBound( 0 )); store. put( "d" ); // 得到键 4
插入显式键仅当键为数值且高于最后一次生成的键时才影响键生成器。
store= db. createObjectStore( "store1" , { autoIncrement: true }); store. put( "a" ); // 得到键 1 store. put( "b" , 3 ); // 使用键 3 store. put( "c" ); // 得到键 4 store. put( "d" , - 10 ); // 使用键 -10 store. put( "e" ); // 得到键 5 store. put( "f" , 6.00001 ); // 使用键 6.0001 store. put( "g" ); // 得到键 7 store. put( "f" , 8.9999 ); // 使用键 8.9999 store. put( "g" ); // 得到键 9 store. put( "h" , "foo" ); // 使用键 "foo" store. put( "i" ); // 得到键 10 store. put( "j" , [ 1000 ]); // 使用键 [1000] store. put( "k" ); // 得到键 11 // 若对象存储区使用 keyPath 且显式键为内联,行为一致。
中止事务会回滚事务期间对键生成器的所有递增。这保证所有回滚一致,因为因崩溃导致的回滚无法提交递增后的键生成器值。
db. createObjectStore( "store" , { autoIncrement: true }); trans1= db. transaction([ "store" ], "readwrite" ); store_t1= trans1. objectStore( "store" ); store_t1. put( "a" ); // 得到键 1 store_t1. put( "b" ); // 得到键 2 trans1. abort(); trans2= db. transaction([ "store" ], "readwrite" ); store_t2= trans2. objectStore( "store" ); store_t2. put( "c" ); // 得到键 1 store_t2. put( "d" ); // 得到键 2
以下示例展示尝试用内联键和键生成器保存对象到对象存储区时的不同行为。
若满足以下条件:
然后由键生成器生成的值用于填充键值。在下方示例中,对象存储的键路径为"foo.bar
"。实际对象没有bar
属性的值,即{ foo: {} }
。
当该对象被保存到对象存储时,bar
属性被赋值为1,因为这是键生成器生成的下一个值。
const store= db. createObjectStore( "store" , { keyPath: "foo.bar" , autoIncrement: true }); store. put({ foo: {} }). onsuccess= function ( e) { const key= e. target. result; console. assert( key=== 1 ); };
如果下列条件均为真:
那么将使用与键路径属性关联的值。自动生成的键不会被使用。下方示例中,对象存储的键路径为"foo.bar
"。实际对象的bar
属性值为10,即{ foo: { bar: 10} }
。保存到对象存储后,bar
属性保持其值10,因为这就是键值。
const store= db. createObjectStore( "store" , { keyPath: "foo.bar" , autoIncrement: true }); store. put({ foo: { bar: 10 } }). onsuccess= function ( e) { const key= e. target. result; console. assert( key=== 10 ); };
下面的示例说明了通过键路径指定的内联键没有对应属性的场景。此时由键生成器生成的值用于填充键值,系统会负责创建所需的属性以满足层级链上的属性依赖。在下方示例中,对象存储的键路径为"foo.bar.baz
"。实际对象没有foo
属性,即{ zip: {} }
。保存到对象存储后,foo
、bar
和baz
属性会依次作为父子关系被创建,直到可以为foo.bar.baz
赋值。该值即为对象存储生成的下一个键。
const store= db. createObjectStore( "store" , { keyPath: "foo.bar.baz" , autoIncrement: true }); store. put({ zip: {} }). onsuccess= function ( e) { const key= e. target. result; console. assert( key=== 1 ); store. get( key). onsuccess= function ( e) { const value= e. target. result; // value 将会是: { zip: {}, foo: { bar: { baz: 1 } } } console. assert( value. foo. bar. baz=== 1 ); }; };
试图在原始值上存储属性会失败并抛出错误。下方第一个示例中,对象存储的键路径为"foo
"。实际对象是值为4
的原始类型。在该原始值上定义属性会失败。
const store= db. createObjectStore( "store" , { keyPath: "foo" , autoIncrement: true }); // 键生成会尝试在该原始值上创建和存储键路径属性 // property on this primitive. store. put( 4 ); // 会抛出 DataError
2.12. 记录快照
注意: 对于索引记录,快照的值是记录的被引用值的副本。 对于对象存储区记录,快照的值即为记录的值。
注意: 对于索引记录,快照的主键为记录的值,即索引中该记录在被引用对象存储区的键。 对于对象存储区记录,快照的主键和键是同一个键,即记录的键。
3. 异常
本文件中使用的异常均为DOMException
或DOMException
派生接口,详见[WEBIDL]。
下表列出了本文件使用的DOMException
名称及其用途说明。
类型 | 说明 |
---|---|
AbortError
| 请求被中止。 |
ConstraintError
| 事务中的变更操作因不满足约束而失败。 |
DataCloneError
| 存储的数据无法通过内部结构化克隆算法进行克隆。 |
DataError
| 操作提供的数据不符合要求。 |
InvalidAccessError
| 对对象执行了无效操作。 |
InvalidStateError
| 在对象上调用了不允许或时机不正确的操作,或在已被删除或移除的源对象上请求操作。 |
NotFoundError
| 操作失败,因为未找到请求的数据库对象。 |
NotReadableError
| 操作失败,因为无法读取底层存储中的请求数据。 |
SyntaxError
| keyPath 参数包含无效的键路径。 |
ReadOnlyError
| 在只读事务中尝试执行变更操作。 |
TransactionInactiveError
| 对当前未活跃或已结束的事务发起了请求。 |
UnknownError
| 操作因与数据库本身无关或未被其它错误覆盖的临时原因失败。 |
VersionError
| 尝试使用低于现有版本号打开数据库。 |
除上述DOMException
外,若操作失败因剩余存储空间不足或达到存储配额且用户拒绝扩容,则应使用QuotaExceededError
异常类型。
注意: 由于多个 Indexed DB 操作可能抛出相同类型错误,且单个操作也可能因多种原因抛出同类型错误,建议实现提供更具体的消息以便开发者定位错误原因。
4. API
API 方法不会阻塞调用线程。所有异步操作会立即返回一个 IDBRequest
实例。该对象初始不包含任何关于操作结果的信息。一旦有结果信息可用,会在请求对象上触发事件,并可通过 IDBRequest
实例的属性获取信息。
这些任务的 任务源 是 数据库访问任务源。
要排队数据库任务,在 queue a task 上对 数据库访问任务源进行操作。
4.1. IDBRequest
接口
IDBRequest
接口用于访问对 数据库和 数据库对象的异步请求结果,通过 事件处理 IDL 属性 [HTML] 实现。
每个异步请求的方法都会返回一个 IDBRequest
对象,通过事件与请求应用通信。设计允许任意数量的请求在任何 数据库
上同时活跃。
下例异步打开一个 数据库,并注册多个事件处理器以应对不同情形。
const request= indexedDB. open( 'AddressBook' , 15 ); request. onsuccess= function ( evt) {...}; request. onerror= function ( evt) {...};
[Exposed =(Window ,Worker )]interface :
IDBRequest EventTarget {readonly attribute any result ;readonly attribute DOMException ?error ;readonly attribute (IDBObjectStore or IDBIndex or IDBCursor )?source ;readonly attribute IDBTransaction ?transaction ;readonly attribute IDBRequestReadyState readyState ; // Event handlers:attribute EventHandler onsuccess ;attribute EventHandler onerror ; };enum {
IDBRequestReadyState ,
"pending" };
"done"
- request .
result
-
请求完成时,返回 结果,若请求失败则返回
undefined
。若请求仍在处理,抛出 "InvalidStateError
"DOMException
。 - request .
error
-
请求完成时,返回 错误(
DOMException
),请求成功则为 null。若请求仍在处理,抛出 "InvalidStateError
"DOMException
。 - request .
source
-
返回请求针对的
IDBObjectStore
、IDBIndex
或IDBCursor
,若为 打开请求则为 null。 - request .
transaction
-
返回请求所在的
IDBTransaction
。若为 打开请求,则在 升级事务存活期间返回升级事务,否则为 null。 - request .
readyState
result
的 getter 步骤如下:
-
如果 this 的 完成标志为 false,则 抛出 "
InvalidStateError
"DOMException
。
error
的 getter 步骤如下:
-
如果 this 的 完成标志为 false,则 抛出 "
InvalidStateError
"DOMException
。
source
的 getter 步骤为返回 this 的 源,若未设置则为 null。
transaction
的 getter 步骤为返回 this 的 事务。
注意: transaction
getter 在某些请求(如 open()
返回的请求)会返回 null。
readyState
的 getter 步骤为:若 this 的 完成标志为 false,则返回 "pending
",否则返回
"done
"。
onsuccess
属性是 事件处理 IDL 属性,其 事件类型为 success
。
onerror
属性是 事件处理 IDL 属性,其 事件类型为 error
事件。
IDBDatabase
上返回 打开请求的方法会使用扩展接口,以便监听 blocked
和 upgradeneeded
事件。
[Exposed =(Window ,Worker )]interface :
IDBOpenDBRequest IDBRequest { // Event handlers:attribute EventHandler onblocked ;attribute EventHandler onupgradeneeded ; };
onblocked
属性是 事件处理 IDL 属性,其 事件类型为 blocked
。
onupgradeneeded
属性是 事件处理 IDL 属性,其 事件类型为 upgradeneeded
。
4.2. 事件接口
本规范通过以下自定义接口触发事件:
[Exposed =(Window ,Worker )]interface :
IDBVersionChangeEvent Event {(
constructor DOMString ,
type optional IDBVersionChangeEventInit = {});
eventInitDict readonly attribute unsigned long long oldVersion ;readonly attribute unsigned long long ?newVersion ; };dictionary :
IDBVersionChangeEventInit EventInit {unsigned long long = 0;
oldVersion unsigned long long ?=
newVersion null ; };
oldVersion
的 getter 步骤为返回其初始化值,表示数据库的前一个版本。
newVersion
的 getter
步骤为返回其初始化值,表示数据库的新版本,若数据库正在被删除则为 null。参见升级数据库步骤。
事件按 DOM § 2.5 构造事件定义构建。
要触发版本变更事件,名称为 e,目标为 target,参数为 oldVersion 和 newVersion,执行如下步骤:
-
令 event 为使用 创建事件得到的
IDBVersionChangeEvent
。 -
设 event 的
type
属性为 e。 -
设 event 的
bubbles
和cancelable
属性为 false。 -
设 event 的
oldVersion
属性为 oldVersion。 -
设 event 的
newVersion
属性为 newVersion。 -
令 legacyOutputDidListenersThrowFlag 为 false。
-
在 target 上分发 event,带参数 legacyOutputDidListenersThrowFlag。
-
返回 legacyOutputDidListenersThrowFlag。
注意: 本算法的返回值并不总被使用。
4.3. IDBFactory
接口
数据库对象通过
IDBFactory
接口方法访问。在支持 Indexed DB 操作的环境中,全局作用域中存在一个实现该接口的对象。
partial interface mixin WindowOrWorkerGlobalScope { [SameObject ]readonly attribute IDBFactory indexedDB ; };
indexedDB
属性为应用提供访问索引数据库功能的机制。
[Exposed =(Window ,Worker )]interface { [
IDBFactory NewObject ]IDBOpenDBRequest open (DOMString ,
name optional [EnforceRange ]unsigned long long ); [
version NewObject ]IDBOpenDBRequest deleteDatabase (DOMString );
name Promise <sequence <IDBDatabaseInfo >>databases ();short cmp (any ,
first any ); };
second dictionary {
IDBDatabaseInfo DOMString ;
name unsigned long long ; };
version
- request = indexedDB .
open
(name) -
尝试打开名为 数据库 的 连接,版本为当前版本,不存在则为 1。请求成功时,request 的
result
为 连接。 - request = indexedDB .
open
(name, version) -
尝试打开名为 数据库 的 连接,版本为指定 version。若数据库存在且版本较低,且有未响应
versionchange
事件而未关闭的连接,请求会被阻塞,直到所有连接关闭,然后进行升级。若数据库存在且版本较高,请求会失败。请求成功时,request 的result
为 连接。 - request = indexedDB .
deleteDatabase
(name) -
尝试删除名为 数据库 的数据库。若数据库存在且有未响应
versionchange
事件而未关闭的连接,请求会被阻塞直到所有连接关闭。请求成功时,request 的result
为 null。 - result = await indexedDB .
databases
() -
返回一个 Promise,解析为给定 存储键 下的数据库名称和版本快照列表。
此 API 用于 Web 应用自省数据库使用(如清理旧版本站点数据)。注意,结果只是快照;无法保证数据收集与响应发送的时序与创建、升级或删除数据库请求的顺序一致(包括本上下文及其它上下文)。
open(name, version)
方法步骤如下:
-
令 storageKey 为以 environment 运行 获取存储键的结果。若失败,则 抛出 "
SecurityError
"DOMException
并终止步骤。 -
令 request 为新的 打开请求。
-
以下步骤并行执行:
-
令 result 为 打开数据库连接的结果,参数为 storageKey、name、(如有)version,否则为 undefined,以及 request。
-
设 request 的 已处理标志为 true。
-
排队数据库任务以执行如下步骤:
-
若 result 为错误:
-
否则:
注意: 若上述步骤导致运行了升级事务,则这些步骤会在事务结束后执行。这样可保证如果即将发生版本升级,成功事件会先在连接上触发,使脚本有机会注册
versionchange
事件监听器。
-
-
-
返回新的
IDBOpenDBRequest
对象,关联 request。
deleteDatabase(name)
方法步骤如下:
-
令 storageKey 为以 environment 运行 获取存储键的结果。若失败,则 抛出 "
SecurityError
"DOMException
并终止步骤。 -
令 request 为新的 打开请求。
-
以下步骤并行执行:
-
令 result 为 删除数据库的结果,参数为 storageKey、name、request。
-
设 request 的 已处理标志为 true。
-
排队数据库任务以执行如下步骤:
-
若 result 为错误,设 request 的 错误为 result, 完成标志为 true, 并 触发事件
error
于 request,其bubbles
和cancelable
属性初始为 true。 -
否则,设 request 的 结果为 undefined, 完成标志为 true, 并 触发版本变更事件 名为
success
于 request,参数为 result 和 null。为什么没有用触发成功事件或 触发错误事件的步骤?
该请求没有关联事务,所以这些步骤(分发前激活事务、分发后停用事务)不适用。此外,此处的
success
事件为IDBVersionChangeEvent
,包含oldVersion
和newVersion
信息。
-
-
-
返回新的
IDBOpenDBRequest
对象,关联 request。
databases()
方法步骤如下:
-
令 storageKey 为以 environment 运行 获取存储键的结果。若失败,则返回 一个被拒绝的 promise,原因为 "
SecurityError
"DOMException
-
令 p 为 新 promise。
-
以下步骤并行执行:
-
返回 p。
databases()
方法是本版本新增。
支持 Chrome 71, Edge 79, Firefox 126, Safari 14。
🚧
- result = indexedDB .
cmp
(key1, key2) -
比较两个值作为 键。若 key1 在 key2 之前返回 -1,key2 在 key1 之前返回 1,相等返回 0。
若任一输入不是有效 键,抛出 "
DataError
"DOMException
。
cmp(first, second)
方法步骤如下:
-
令 a 为用 first 转换为键的结果。异常重新抛出。
-
若 a 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
令 b 为用 second 转换为键的结果。异常重新抛出。
-
若 b 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
返回用 a 和 b 比较两个键 的结果。
4.4. IDBDatabase
接口
IDBDatabase
接口表示与 数据库 的 连接。
当关联的 连接 的 关闭待定标志为 false
且注册了类型为 abort
、
error
或 versionchange
的一个或多个事件监听器时,IDBDatabase
对象不得被垃圾回收。若对象被回收,关联 连接必须被 关闭。
[Exposed =(Window ,Worker )]interface :
IDBDatabase EventTarget {readonly attribute DOMString name ;readonly attribute unsigned long long version ;readonly attribute DOMStringList objectStoreNames ; [NewObject ]IDBTransaction transaction ((DOMString or sequence <DOMString >),
storeNames optional IDBTransactionMode = "readonly",
mode optional IDBTransactionOptions = {});
options undefined close (); [NewObject ]IDBObjectStore createObjectStore (DOMString ,
name optional IDBObjectStoreParameters = {});
options undefined deleteObjectStore (DOMString ); // Event handlers:
name attribute EventHandler onabort ;attribute EventHandler onclose ;attribute EventHandler onerror ;attribute EventHandler onversionchange ; };enum {
IDBTransactionDurability ,
"default" ,
"strict" };
"relaxed" dictionary {
IDBTransactionOptions IDBTransactionDurability = "default"; };
durability dictionary { (
IDBObjectStoreParameters DOMString or sequence <DOMString >)?=
keyPath null ;boolean =
autoIncrement false ; };
name
的 getter 步骤为返回 this 关联的 数据库的 名称。
注意:
name
属性即使 this 的 关闭待定标志为 true 也不会变化。即该属性在 IDBDatabase
实例生命周期内保持不变。
version
的 getter 步骤为返回 this 的 版本。
这是否等同于 数据库 的 版本?
只要 连接是打开的,这属性与连接的 数据库的 版本一致。但一旦 连接被 关闭,此属性不会反映之后 升级事务带来的变更。- connection .
objectStoreNames
-
返回数据库中所有 对象存储区的名称列表。
- store = connection .
createObjectStore
(name [, options]) -
使用指定 name 和 options 创建新的 对象存储区并返回新的
IDBObjectStore
。如果不是在 升级事务内调用,则抛出 "
InvalidStateError
"DOMException
。 - connection .
deleteObjectStore
(name) -
删除指定 name 的 对象存储区。
如果不是在 升级事务内调用,则抛出 "
InvalidStateError
"DOMException
。
objectStoreNames
的 getter 步骤如下:
这是否等同于 数据库的 对象存储区 名称?
只要 连接是打开的,这属性与连接的 数据库的 对象存储区 名称一致。但一旦 连接被 关闭,此属性不会反映之后 升级事务带来的变更。createObjectStore(name, options)
方法步骤如下:
-
令 transaction 为 database 的 升级事务(不为 null 时),否则 抛出 "
InvalidStateError
"DOMException
。 -
若 transaction 的 状态不是 活动,则 抛出 "
TransactionInactiveError
"DOMException
。 -
令 keyPath 为 options 的
keyPath
成员(不为 undefined 或 null 时),否则为 null。 -
若 keyPath 不为 null 且不是 有效键路径,则 抛出 "
SyntaxError
"DOMException
。 -
若 对象存储区中已存在 name,则 抛出 "
ConstraintError
"DOMException
。 -
令 autoIncrement 为 options 的
autoIncrement
成员。 -
若 autoIncrement 为 true 且 keyPath 为空字符串或任意序列(无论是否为空),则 抛出 "
InvalidAccessError
"DOMException
。 -
令 store 为 database 中新建的 对象存储区。设其 名称为 name。若 autoIncrement 为 true,则该对象存储区使用 键生成器。若 keyPath 不为 null,则其 键路径为 keyPath。
-
返回与 store 和 transaction 关联的新 对象存储区句柄。
该方法在连接的 数据库中新建并返回指定名称的 对象存储区。注意,只能在 升级事务内调用此方法。
该方法会同步修改调用实例上的 objectStoreNames
属性。
在某些实现中,队列任务创建 对象存储区后,createObjectStore()
方法已经返回,但实现可能遇到问题(如异步插入新建对象存储区元数据,或因配额需用户授权)。此时仍需创建并返回 IDBObjectStore
对象。若创建对象存储区失败,必须用合适错误中止事务(如因配额失败则用 QuotaExceededError
)。
deleteObjectStore(name)
方法步骤如下:
-
令 transaction 为 database 的 升级事务(不为 null 时),否则 抛出 "
InvalidStateError
"DOMException
。 -
若 transaction 的 状态不是 活动,则 抛出 "
TransactionInactiveError
"DOMException
。 -
令 store 为 database 中名为 name 的 对象存储区,否则 抛出 "
NotFoundError
"DOMException
。 -
销毁 store。
该方法销毁连接的 数据库中指定名称的 对象存储区。注意,只能在 升级事务内调用此方法。
该方法会同步修改调用实例上的 objectStoreNames
属性。
- transaction = connection .
transaction
(scope [, mode [, options ] ]) -
新建一个 事务,作用域为 scope(可为单个 对象存储区名称或名称数组),mode("
readonly
" 或 "readwrite
"),以及额外 options(如durability
为 "default
"、"strict
" 或 "relaxed
")。默认 mode 为 "
readonly
",durability
默认值为 "default
"。 - connection .
close
()
transaction(storeNames, mode, options)
方法步骤如下:
-
如果该连接关联有存活的升级事务, 抛出"
InvalidStateError
"DOMException
。 -
如果 this 的 关闭待处理标志为 true,则 抛出 "
InvalidStateError
"DOMException
。 -
令 scope 为 storeNames 是序列时的唯一字符串集合,否则为仅包含一个等于 storeNames 的字符串的集合。
-
如果 scope 中任何字符串不是该已连接的数据库中的对象存储区名称,抛出 "
NotFoundError
"DOMException
。 -
如果 scope 为空,抛出 "
InvalidAccessError
"DOMException
。 -
令 transaction 为新创建的事务,参数为本连接、mode、options的
durability
成员,以及 scope 中命名的对象存储区集合。 -
返回表示 transaction 的
IDBTransaction
对象。
durability
选项为本版本新增。
支持 Chrome 82, Edge 82, Firefox 126, Safari 15。
🚧
注意: 创建的 transaction 将遵循 生命周期规则。
注意:
连接在所有未完成事务完成前不会真正关闭。后续调用 close()
无效果。
onabort
属性是 事件处理 IDL 属性,其 事件类型为 abort
。
onclose
属性是 事件处理 IDL 属性,其 事件类型为 close
。
onerror
属性是 事件处理 IDL 属性,其 事件类型为 error
。
onversionchange
属性是 事件处理 IDL 属性,其 事件类型为 versionchange
。
4.5. IDBObjectStore
接口
IDBObjectStore
接口表示一个对象存储区句柄。
[Exposed =(Window ,Worker )]interface {
IDBObjectStore attribute DOMString name ;readonly attribute any keyPath ;readonly attribute DOMStringList indexNames ; [SameObject ]readonly attribute IDBTransaction transaction ;readonly attribute boolean autoIncrement ; [NewObject ]IDBRequest put (any ,
value optional any ); [
key NewObject ]IDBRequest add (any ,
value optional any ); [
key NewObject ]IDBRequest delete (any ); [
query NewObject ]IDBRequest clear (); [NewObject ]IDBRequest get (any ); [
query NewObject ]IDBRequest getKey (any ); [
query NewObject ]IDBRequest getAll (optional any ,
queryOrOptions optional [EnforceRange ]unsigned long ); [
count NewObject ]IDBRequest getAllKeys (optional any ,
queryOrOptions optional [EnforceRange ]unsigned long ); [
count NewObject ]IDBRequest getAllRecords (optional IDBGetAllOptions = {}); [
options NewObject ]IDBRequest count (optional any ); [
query NewObject ]IDBRequest openCursor (optional any ,
query optional IDBCursorDirection = "next"); [
direction NewObject ]IDBRequest openKeyCursor (optional any ,
query optional IDBCursorDirection = "next");
direction IDBIndex index (DOMString ); [
name NewObject ]IDBIndex createIndex (DOMString , (
name DOMString or sequence <DOMString >),
keyPath optional IDBIndexParameters = {});
options undefined deleteIndex (DOMString ); };
name dictionary {
IDBIndexParameters boolean =
unique false ;boolean =
multiEntry false ; };dictionary {
IDBGetAllOptions any =
query null ; [EnforceRange ]unsigned long ;
count IDBCursorDirection = "next"; };
direction
- store .
name
-
返回该存储的名称。
- store .
name
= newName -
将该存储的名称更新为newName。
如果不是在升级事务中调用,则抛出"
InvalidStateError
"DOMException
。 - store .
keyPath
-
返回该存储的键路径,如果没有则返回 null。
- store .
indexNames
-
返回存储内所有索引的名称列表。
- store .
transaction
-
返回关联的事务。
- store .
autoIncrement
-
如果该存储有键生成器则返回 true,否则返回 false。
name
的 getter 步骤为返回 this 的 名称。
这和对象存储区的名称一样吗?
只要事务还没结束, 这里返回的就是关联对象存储区的名称。但一旦事务结束,此属性不会反映后续升级事务修改的内容。name
的 setter 步骤如下:
-
令 name 为给定值。
-
如果 store 已被删除, 抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 不是升级事务, 抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是active, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 store 的 名称等于 name,则终止步骤。
-
如果 store 的数据库中已存在命名为name的对象存储区, 抛出 "
ConstraintError
"DOMException
。 -
设 store 的 名称为 name。
keyPath
的 getter 步骤为返回 this 的 对象存储区的键路径,若没有则为
null。键路径按 DOMString
(若为字符串)或
sequence<
(若为字符串列表)转换,详见
[WEBIDL]。
DOMString
>
注意:
返回值并不是创建对象存储区时使用的同一个实例。但如果此属性返回对象(即
Array
),每次访问时都是同一个对象实例。更改对象属性不会影响对象存储区。
indexNames
的 getter 步骤如下:
这和对象存储区的索引index名称列表一样吗?
只要事务还没结束, 这里返回的就是关联对象存储区的索引index名称列表。但一旦事务结束,此属性不会反映后续升级事务修改的内容。transaction
的 getter 步骤为返回 this 的 事务。
autoIncrement
的 getter 步骤为:如果 this 的 对象存储区有键生成器则返回 true,否则返回 false。
ReadOnlyError
"
DOMException
,在事务未激活时调用会抛出"TransactionInactiveError
"
DOMException
。
- request = store
.
put
(value [, key])- request = store .
add
(value [, key]) - request = store .
-
在 store 中用给定 value 和 key 添加或更新记录。
如果存储区使用内联键且指定了 key,会抛出 "
DataError
"DOMException
。如果使用
put()
,已存在的同键记录会被替换。如果使用add()
,且已存在同键记录,则 request 会失败,其error
被设为 "ConstraintError
"DOMException
。 - request = store .
delete
(query) -
删除 store 中与给定 键或 键区间 query 匹配的记录。
成功时,request 的
result
为undefined
。 - request = store .
clear
() -
删除 store 中所有记录。
成功时,request 的
result
为undefined
。
put(value, key)
方法步骤为:返回运行 add or put,参数为 this、value、key 和
no-overwrite flag 为 false 的结果。
add(value, key)
方法步骤为:返回运行 add or put,参数为 this、value、key 和
no-overwrite flag 为 true 的结果。
要add or put,参数为 handle、value、key、no-overwrite flag,执行如下步骤:
-
令 transaction 为 handle 的 事务。
-
令 store 为 handle 的 对象存储区。
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 transaction 是只读事务, 抛出 "
ReadOnlyError
"DOMException
。 -
如果 store 使用内联键且给定了 key, 抛出 "
DataError
"DOMException
。 -
如果 store 使用外部键且没有键生成器且未给定 key,抛出 "
DataError
"DOMException
。 -
如果给定了 key,则:
-
令 r 为用 key 转换为键的结果。异常重新抛出。
-
如果 r 是 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
令 key 为 r。
-
-
令 targetRealm 为 user-agent 定义的 Realm。
-
令 clone 为 value 在 targetRealm 下于 transaction 期间的 克隆。异常重新抛出。
为什么要创建副本?
存储值时会序列化。这里当作副本处理允许规范其他算法把它当 ECMAScript 值,但如果行为差异不可观察,实际实现可优化。 -
如果 store 使用内联键,则:
-
如果 kpk 无效,抛出 "
DataError
"DOMException
。 -
如果 kpk 非 failure,令 key 为 kpk。
-
否则(kpk 为 failure):
-
如果 store 没有键生成器, 抛出 "
DataError
"DOMException
。 -
如果用 clone 和 store 的 键路径 检查能否注入键返回 false, 抛出 "
DataError
"DOMException
。
-
-
令 operation 为用 store、clone、key 和 no-overwrite flag 运行 存储记录到对象存储区的算法。
-
返回用 handle 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
delete(query)
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 transaction 是只读事务, 抛出 "
ReadOnlyError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间(true)。异常重新抛出。
-
令 operation 为用 store 和 range 运行 从对象存储区删除记录的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
query 参数可以是键或键区间(IDBKeyRange
)用于定位要删除的记录。
注意: 与其他接受键或键区间的方法不同,不允许此方法的 key 为 null。这样可减少小 bug 导致清空整个对象存储区的风险。
clear()
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 transaction 是只读事务, 抛出 "
ReadOnlyError
"DOMException
。 -
令 operation 为用 store 运行 清空对象存储区的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
TransactionInactiveError
"
DOMException
:
- request = store .
get
(query) - request = store .
getKey
(query) - request = store .
getAll
(query [, count])- request = store .
getAll
({query, count, direction}) - request = store .
-
检索所有匹配 query 中给定键或键区间的记录的值(若给定 count 则最多返回该数量)。 direction 选项设为 "
next
" 可返回前 count 个值,设为 "prev
" 可返回最后 count 个值。 - request = store .
getAllKeys
(query [, count])- request = store .
getAllKeys
({query, count, direction}) - request = store .
-
检索所有匹配 query 中给定键或键区间的记录的键(若给定 count 则最多返回该数量)。 direction 选项设为 "
next
" 可返回前 count 个键,设为 "prev
" 可返回最后 count 个键。 - request = store .
getAllRecords
({query, count, direction}) -
query 选项指定要匹配的键或键区间。count 限制匹配记录数量。direction 设为 "
next
" 可返回前 count 个记录,设为 "prev
" 可返回最后 count 个记录。 - request = store .
count
(query) -
成功时,request 的
result
为记录数。
get(query)
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间(true)。异常重新抛出。
-
令 operation 为用 当前 Realm 记录、store 和 range 运行 从对象存储区检索值的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于定位要检索的记录值。若指定区间,则检索该区间内第一个现有值。
注意:
如果给定键不存在记录,该方法的结果与存在记录但值为 undefined
的情况相同。若需区分两者,可用 openCursor()
方法,若有记录则游标值为 undefined
,否则无游标。
getKey(query)
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间(true)。异常重新抛出。
-
令 operation 为用 store 和 range 运行 从对象存储区检索键的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
query 参数可以是键或键区间(IDBKeyRange
)用于定位要检索的记录键。若指定区间,则检索该区间内第一个现有键。
getAll(queryOrOptions, count)
方法步骤如下:
-
返回用 创建多项检索请求,参数为 当前 Realm 记录、this、"value"、queryOrOptions 和(如给定)count 的结果。异常重新抛出。
getAllKeys(queryOrOptions, count)
方法步骤如下:
-
返回用 创建多项检索请求,参数为 当前 Realm 记录、this、"key"、queryOrOptions 和(如给定)count 的结果。异常重新抛出。
getAllRecords(options)
方法步骤如下:
-
返回用 创建多项检索请求,参数为 当前 Realm 记录、this、"record" 和 options 的结果。异常重新抛出。
count(query)
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间。异常重新抛出。
-
令 operation 为用 store 和 range 运行 统计区间内记录数的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于定位要计数的记录。若为 null
或未给定,则使用无限键区间。
TransactionInactiveError
"
DOMException
:
- request = store .
openCursor
([query [, direction = "next"]]) -
打开一个游标,遍历所有与 query 匹配的记录,按 direction 顺序排列。如果 query 为 null,则匹配 store 中所有记录。
成功时,request 的
result
为指向第一个匹配记录的IDBCursorWithValue
,若无匹配记录则为 null。 - request = store .
openKeyCursor
([query [, direction = "next"]]) -
打开一个游标,并设置仅键标志为 true,遍历所有与 query 匹配的记录,按 direction 顺序排列。如果 query 为 null,则匹配 store 中所有记录。
openCursor(query, direction)
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间。异常重新抛出。
-
令 cursor 为新游标,其源句柄为 this,位置为 undefined,方向为 direction,已取值标志为 false,键和值为 undefined,区间为 range,仅键标志为 false。
-
令 operation 为用 遍历游标,参数为 当前 Realm 记录 和 cursor。
-
设 cursor 的 request 为 request。
-
返回 request。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于指定游标的区间。如为 null 或未给定,则使用无限键区间。
openKeyCursor(query, direction)
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间。异常重新抛出。
-
令 cursor 为新游标,其源句柄为 this,位置为 undefined,方向为 direction,已取值标志为 false,键和值为 undefined,区间为 range,仅键标志为 true。
-
令 operation 为用 遍历游标,参数为 当前 Realm 记录 和 cursor。
-
设 cursor 的 request 为 request。
-
返回 request。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于指定游标的区间。如为 null 或未给定,则使用无限键区间。
- index = store . index(name)
-
返回 store 中命名为 name 的
IDBIndex
。 - index = store .
createIndex
(name, keyPath [, options]) -
在 store 中创建一个新的 索引,指定 name、keyPath 和 options,并返回新的
IDBIndex
。如果 keyPath 和 options 定义的约束无法满足 store 中已存在的数据,则 升级事务会中止,抛出 "ConstraintError
"DOMException
。如果不是在升级事务中调用,则抛出"
InvalidStateError
"DOMException
。 - store .
deleteIndex
(name) -
删除 store 中命名为 name 的 索引。
如果不是在升级事务中调用,则抛出"
InvalidStateError
"DOMException
。
createIndex(name, keyPath, options)
方法步骤如下:
-
如果 transaction 不是升级事务, 抛出 "
InvalidStateError
"DOMException
。 -
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 store 中已存在名为 name 的索引,抛出 "
ConstraintError
"DOMException
。 -
如果 keyPath 不是有效键路径,抛出 "
SyntaxError
"DOMException
。 -
令 unique 为 options 的
unique
成员。 -
令 multiEntry 为 options 的
multiEntry
成员。 -
如果 keyPath 是序列且 multiEntry 为 true,抛出 "
InvalidAccessError
"DOMException
。 -
令 index 为 store 中的新索引。设 index 的 名称为 name,键路径为 keyPath, 唯一标志为 unique, multiEntry 标志为 multiEntry。
该方法在对象存储区中新建并返回具有指定名称的索引。注意此方法必须仅在升级事务中调用。
请求创建的索引可以对索引引用的对象存储区数据设置约束,
例如要求索引键路径所引用的值唯一。如果引用对象存储区已有数据违反这些约束,不能导致 createIndex()
抛出异常或影响其返回结果。实现仍需创建并返回 IDBIndex
对象,并且实现必须 排队数据库任务以中止用于 createIndex()
调用的升级事务。
此方法同步修改调用的 IDBObjectStore
实例上的 indexNames
属性。虽然此方法不返回 IDBRequest
对象,但索引创建本身会作为 升级事务中的异步请求处理。
在某些实现中,createIndex 方法返回后,异步创建索引时可能会遇到问题。例如在新索引的元数据异步插入数据库、或因配额需要请求用户权限的情况下。此类实现仍需创建并返回 IDBIndex
对象,一旦确认创建索引失败,必须使用合适错误运行 中止事务步骤。例如因配额失败需用 QuotaExceededError
,因唯一性约束无法创建索引时需用
"ConstraintError
"
DOMException
。
如下示例可观察到索引的异步创建:
const request1= objectStore. put({ name: "betty" }, 1 ); const request2= objectStore. put({ name: "betty" }, 2 ); const index= objectStore. createIndex( "by_name" , "name" , { unique: true });
当调用 createIndex()
时,两个
请求都未执行。第二个请求执行时会创建重复名称。由于索引创建被视为异步请求,索引的唯一性约束不会导致第二个
请求失败。相反,当索引创建且约束失败时,事务会被中止。
index(name)
方法步骤如下:
-
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态为finished, 抛出 "
InvalidStateError
"DOMException
。 -
令 index 为 索引集合中名为 name 的索引,如不存在则 抛出 "
NotFoundError
"DOMException
。
注意:
多次在同一 IDBObjectStore
实例上用同名调用该方法,返回的都是同一个 IDBIndex
实例。
注意:
返回的 IDBIndex
实例仅关联于当前 IDBObjectStore
实例。在不同实例上调用同名方法会返回不同的 IDBIndex
实例。
deleteIndex(name)
方法步骤如下:
-
如果 transaction 不是升级事务, 抛出 "
InvalidStateError
"DOMException
。 -
如果 store 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 index 为 store 中名为 name 的索引,如不存在则 抛出 "
NotFoundError
"DOMException
。 -
销毁 index。
此方法销毁对象存储区中指定名称的索引。注意此方法必须仅在升级事务中调用。
该方法同步修改调用的 IDBObjectStore
实例上的 indexNames
属性。虽然此方法不返回 IDBRequest
对象,但索引删除本身会作为 升级事务中的异步请求处理。
4.6. IDBIndex
接口
[Exposed =(Window ,Worker )]interface {
IDBIndex attribute DOMString name ; [SameObject ]readonly attribute IDBObjectStore objectStore ;readonly attribute any keyPath ;readonly attribute boolean multiEntry ;readonly attribute boolean unique ; [NewObject ]IDBRequest get (any ); [
query NewObject ]IDBRequest getKey (any ); [
query NewObject ]IDBRequest getAll (optional any ,
queryOrOptions optional [EnforceRange ]unsigned long ); [
count NewObject ]IDBRequest getAllKeys (optional any ,
queryOrOptions optional [EnforceRange ]unsigned long ); [
count NewObject ]IDBRequest getAllRecords (optional IDBGetAllOptions = {}); [
options NewObject ]IDBRequest count (optional any ); [
query NewObject ]IDBRequest openCursor (optional any ,
query optional IDBCursorDirection = "next"); [
direction NewObject ]IDBRequest openKeyCursor (optional any ,
query optional IDBCursorDirection = "next"); };
direction
- index .
name
-
返回索引的名称。
- index .
name
= newName -
将索引的名称更新为newName。
如果不是在升级事务中调用,则抛出"
InvalidStateError
"DOMException
。 - index .
objectStore
-
返回索引所属的
IDBObjectStore
。 - index . keyPath
-
返回索引的键路径。
- index . multiEntry
-
如果索引的multiEntry 标志为 true,则返回 true。
- index . unique
-
如果索引的唯一标志为 true,则返回 true。
name
的 getter 步骤为返回 this 的 名称。
和索引的名称一样吗?
只要事务还没结束, 这里返回的就是关联索引的名称。但一旦事务结束,此属性不会反映后续升级事务修改的内容。name
的 setter 步骤如下:
-
令 name 为给定值。
-
如果 transaction 不是升级事务, 抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 index 或其对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 index 的 名称等于 name,则终止步骤。
-
如果 index 的对象存储区中已存在名为 name 的索引, 抛出 "
ConstraintError
"DOMException
。 -
设 index 的 名称为 name。
objectStore
的 getter 步骤为返回 this 的 对象存储区句柄。
keyPath
的 getter 步骤为返回 this 的 索引的
键路径。键路径按
DOMString
(字符串)或
sequence<
(字符串列表)转换,详见
[WEBIDL]。
DOMString
>
注意:
返回值不是创建索引时使用的同一实例。但如果此属性返回对象(即
Array
),每次访问都是同一个对象实例。更改对象属性不会影响索引。
multiEntry
的 getter 步骤为返回 this 的 索引的 multiEntry 标志。
unique
的 getter 步骤为返回 this 的 索引的 唯一标志。
TransactionInactiveError
"
DOMException
:
- request = index .
get
(query) - request = index .
getKey
(query) - request = index .
getAll
(query [, count])- request = index .
getAll
({query, count, direction}) - request = index .
-
检索所有匹配 query 中给定键或键区间的记录的值(若给定 count 则最多返回该数量)。 direction 选项设为 "
next
" 可返回前 count 个值,"prev
" 可返回最后 count 个值。设为 "nextunique
" 或 "prevunique
" 可在匹配到重复索引键后仅返回第一个记录,忽略后续重复键。 - request = index .
getAllKeys
(query [, count])- request = index .
getAllKeys
({query, count, direction}) - request = index .
-
检索所有匹配 query 中给定键或键区间的记录的键(若给定 count 则最多返回该数量)。 direction 选项设为 "
next
" 可返回前 count 个键,"prev
" 可返回最后 count 个键。设为 "nextunique
" 或 "prevunique
" 可在匹配到重复索引键后仅返回第一个记录,忽略后续重复键。 - request = index .
getAllRecords
({query, count, direction}) -
query 选项指定要匹配的键或键区间。count 限制匹配记录数量。direction 设为 "
next
" 可返回前 count 个记录,"prev
" 可返回最后 count 个记录。设为 "nextunique
" 或 "prevunique
" 可跳过重复索引键,仅返回第一个记录。成功时,request 的
result
为Array
,每个成员为IDBRecord
。通过IDBRecord’s key
获取记录的索引键,通过IDBRecord’s primaryKey
获取主键。 - request = index .
count
(query) -
成功时,request 的
result
为记录数。
get(query)
方法步骤如下:
-
如果 index 或其 对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间(true)。异常重新抛出。
-
令 operation 为用 从索引检索引用值,参数为 当前 Realm 记录、index 和 range 的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于定位要检索的引用值。如指定区间,则检索该区间内第一个现有记录。
注意:
若指定键无记录,该方法的结果与存在记录但值为 undefined
的情况相同。若需区分两者,可用 openCursor()
,若有记录游标值为
undefined
,否则无游标。
getKey(query)
方法步骤如下:
-
如果 index 或其 对象存储区已被删除, 抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间(true)。异常重新抛出。
-
令 operation 为用 index 和 range 运行 从索引检索值的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于定位要检索的记录键。如指定区间,则检索该区间内第一个现有键。
getAll(queryOrOptions, count)
方法步骤如下:
-
返回用 创建多项检索请求,参数为 当前 Realm 记录、this、"value"、queryOrOptions 和(如给定)count 的结果。异常重新抛出。
getAllKeys(queryOrOptions, count)
方法步骤如下:
-
返回用 创建多项检索请求,参数为 当前 Realm 记录、this、"key"、queryOrOptions 和(如给定)count 的结果。异常重新抛出。
getAllRecords(options)
方法步骤如下:
-
返回用 创建多项检索请求,参数为 当前 Realm 记录、this、"record" 和 options 的结果。异常重新抛出。
count(query)
方法步骤如下:
-
如果 index 或其 对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间。异常重新抛出。
-
令 operation 为用 index 和 range 运行 统计区间内记录数的算法。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于定位要计数的记录。如为 null 或未给定,则使用无限键区间。
TransactionInactiveError
"
DOMException
:
- request = index .
openCursor
([query [, direction = "next"]]) -
打开一个游标,遍历所有与 query 匹配的记录,按 direction 顺序排列。如果 query 为 null,则匹配 index 中所有记录。
成功时,request 的
result
为IDBCursorWithValue
,若无匹配记录则为 null。 - request = index .
openKeyCursor
([query [, direction = "next"]]) -
打开一个游标,并设置仅键标志为 true,遍历所有与 query 匹配的记录,按 direction 顺序排列。如果 query 为 null,则匹配 index 中所有记录。
openCursor(query, direction)
方法步骤如下:
-
如果 index 或其 对象存储区已被删除, 抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间。异常重新抛出。
-
令 cursor 为新游标,其源句柄为 this,位置为 undefined,方向为 direction,已取值标志为 false,键和值为 undefined,区间为 range,仅键标志为 false。
-
令 operation 为用 遍历游标,参数为 当前 Realm 记录 和 cursor。
-
设 cursor 的 request 为 request。
-
返回 request。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于指定游标的区间。如为 null 或未给定,则使用无限键区间。
openKeyCursor(query, direction)
方法步骤如下:
-
如果 index 或其 对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 transaction 的 状态不是 active, 抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为用 query 运行 转换为键区间。异常重新抛出。
-
令 cursor 为新游标,其源句柄为 this,位置为 undefined,方向为 direction,已取值标志为 false,键和值为 undefined,区间为 range,仅键标志为 true。
-
令 operation 为用 遍历游标,参数为 当前 Realm 记录 和 cursor。
-
设 cursor 的 request 为 request。
-
返回 request。
注意:
query 参数可以是键或键区间(IDBKeyRange
),用于指定游标的区间。如为 null 或未给定,则使用无限键区间。
4.7. IDBKeyRange
接口
IDBKeyRange
接口表示一个
键区间。
[Exposed =(Window ,Worker )]interface {
IDBKeyRange readonly attribute any lower ;readonly attribute any upper ;readonly attribute boolean lowerOpen ;readonly attribute boolean upperOpen ; // Static construction methods: [NewObject ]static IDBKeyRange only (any ); [
value NewObject ]static IDBKeyRange lowerBound (any ,
lower optional boolean =
open false ); [NewObject ]static IDBKeyRange upperBound (any ,
upper optional boolean =
open false ); [NewObject ]static IDBKeyRange bound (any ,
lower any ,
upper optional boolean =
lowerOpen false ,optional boolean =
upperOpen false );boolean includes (any ); };
key
lower
的 getter 步骤为:如果 下界不为 null,则用 将键转换为值,否则返回 undefined。
upper
的 getter 步骤为:如果 上界不为 null,则用 将键转换为值,否则返回 undefined。
lowerOpen
的 getter 步骤为:返回 下界开放标志。
upperOpen
的 getter 步骤为:返回 上界开放标志。
- range =
IDBKeyRange
.only
(key) -
返回一个仅包含 key 的新的
IDBKeyRange
。 - range =
IDBKeyRange
.lowerBound
(key [, open = false]) -
返回一个下界为 key、无上界的新
IDBKeyRange
。如果 open 为 true,则 key 不包含在区间内。 - range =
IDBKeyRange
.upperBound
(key [, open = false]) -
返回一个无下界、上界为 key 的新
IDBKeyRange
。如果 open 为 true,则 key 不包含在区间内。 - range =
IDBKeyRange
.bound
(lower, upper [, lowerOpen = false [, upperOpen = false]]) -
返回一个从 lower 到 upper 的新
IDBKeyRange
。如果 lowerOpen 为 true,则下界不包含在区间内;如果 upperOpen 为 true,则上界不包含在区间内。
only(value)
方法步骤如下:
-
令 key 为用 value 转换为键的结果。异常重新抛出。
-
如果 key 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
创建并返回一个仅包含 key 的键区间。
lowerBound(lower, open)
方法步骤如下:
-
令 lowerKey 为用 lower 转换为键的结果。异常重新抛出。
-
如果 lowerKey 无效,抛出 "
DataError
"DOMException
。 -
创建并返回一个下界为 lowerKey、下界开放标志为 open、上界为 null、上界开放标志为 true 的键区间。
upperBound(upper, open)
方法步骤如下:
-
令 upperKey 为用 upper 转换为键的结果。异常重新抛出。
-
如果 upperKey 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
创建并返回一个下界为 null、下界开放标志为 true、上界为 upperKey、上界开放标志为 open 的键区间。
bound(lower, upper, lowerOpen, upperOpen)
方法步骤如下:
-
令 lowerKey 为用 lower 转换为键的结果。异常重新抛出。
-
如果 lowerKey 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
令 upperKey 为用 upper 转换为键的结果。异常重新抛出。
-
如果 upperKey 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
如果 lowerKey 大于 upperKey,抛出 "
DataError
"DOMException
。 -
创建并返回一个下界为 lowerKey、下界开放标志为 lowerOpen、上界为 upperKey、上界开放标志为 upperOpen 的键区间。
- range .
includes
(key) -
如果 key 在区间内则返回 true,否则返回 false。
includes(key)
方法步骤如下:
-
令 k 为用 key 转换为键的结果。异常重新抛出。
-
如果 k 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
如果 k 在区间内则返回 true,否则返回 false。
4.8. IDBRecord
接口
[Exposed =(Window ,Worker )]interface {
IDBRecord readonly attribute any key ;readonly attribute any primaryKey ;readonly attribute any value ; };
key
的 getter 步骤为用 将键转换为值,参数为 this 的 键。
primaryKey
的 getter 步骤为用 将键转换为值,参数为 this 的 主键。
value
的 getter 步骤为返回 this 的 值。
4.9. IDBCursor
接口
游标对象实现了 IDBCursor
接口。每个游标只对应一个 IDBCursor
实例,同时可以使用任意数量的游标。
[Exposed =(Window ,Worker )]interface {
IDBCursor readonly attribute (IDBObjectStore or IDBIndex )source ;readonly attribute IDBCursorDirection direction ;readonly attribute any key ;readonly attribute any primaryKey ; [SameObject ]readonly attribute IDBRequest request ;undefined advance ([EnforceRange ]unsigned long );
count undefined continue (optional any );
key undefined continuePrimaryKey (any ,
key any ); [
primaryKey NewObject ]IDBRequest update (any ); [
value NewObject ]IDBRequest delete (); };enum {
IDBCursorDirection ,
"next" ,
"nextunique" ,
"prev" };
"prevunique"
- cursor .
source
-
返回游标打开自的
IDBObjectStore
或IDBIndex
。 - cursor .
direction
-
返回游标的方向 ("
next
", "nextunique
", "prev
" 或 "prevunique
")。 - cursor .
key
-
返回游标的键。 若游标正在前进或已结束,抛出 "
InvalidStateError
"DOMException
。 - cursor .
primaryKey
-
返回游标的有效键。 若游标正在前进或已结束,抛出 "
InvalidStateError
"DOMException
。 - cursor .
request
-
返回用于获取此游标的请求。
source
的 getter 步骤为返回 this 的
源句柄。
注意:
source
属性即使游标正在迭代、已迭代到末尾或其 事务未
激活,也不会返回 null 或抛出异常。
direction
的 getter 步骤为返回 this 的
方向。
key
的 getter 步骤为用游标当前的键运行 将键转换为值 的结果。
注意:
如果 key
返回对象(如 Date
或
Array
),则在游标的键改变前,每次访问都是同一个对象实例。修改该对象会被所有访问该值的人看到,但不会修改数据库内容。
primaryKey
的 getter 步骤为用游标当前的有效键运行 将键转换为值 的结果。
注意:
如果 primaryKey
返回对象(如 Date
或 Array
),则在游标的有效键改变前,每次访问都是同一个对象实例。修改该对象会被所有访问该值的人看到,但不会修改数据库内容。
request
的 getter 步骤为返回 this 的
请求。
request
属性为本版本新增。
支持 Chrome 76、Edge 79、Firefox 77 和 Safari 15。
🚧
success
事件中触发。
result
如果有记录在区间内,则为同一个游标,否则为
undefined
。
若游标正在前进时调用这些方法,将抛出 "InvalidStateError
"
DOMException
。
下列方法在事务未激活时调用会抛出"TransactionInactiveError
"
DOMException
。
- cursor .
advance
(count) -
将游标推进到区间内下一个 count 个记录。
- cursor .
continue
() -
将游标推进到区间内下一个记录。
- cursor .
continue
(key) -
将游标推进到区间内下一个匹配或大于 key 的记录。
- cursor .
continuePrimaryKey
(key, primaryKey) -
将游标推进到区间内下一个匹配或大于 key 和 primaryKey 的记录。若源不是索引,则抛出 "
InvalidAccessError
"DOMException
。
advance(count)
方法步骤如下:
-
如果 transaction 的 状态不是 激活, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 this 的 源或 有效对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 this 的 已取值标志为 false(表示游标正在迭代或已到末尾), 抛出 "
InvalidStateError
"DOMException
。 -
设 request 的 已处理标志为 false。
-
设 request 的 完成标志为 false。
-
令 operation 为用 遍历游标,参数为 当前 Realm 记录、this 和 count。
注意:
在新的游标数据加载前多次调用此方法(如在同一个 onsuccess 回调里连续调用两次 advance()
),第二次调用会因游标的已取值标志被设为
false 而抛出 "InvalidStateError
"
DOMException
。
continue(key)
方法步骤如下:
-
如果 transaction 的 状态不是 激活, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 this 的 源或 有效对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 this 的 已取值标志为 false(游标正在迭代或已到末尾), 抛出 "
InvalidStateError
"DOMException
。 -
如果给定了 key,则:
-
设 request 的 已处理标志为 false。
-
设 request 的 完成标志为 false。
-
令 operation 为用 遍历游标,参数为 当前 Realm 记录、this 和(如给定)key。
注意:
在新的游标数据加载前多次调用此方法(如在同一个 onsuccess 回调里连续调用两次 continue()
),第二次调用会因游标的已取值标志被设为
false 而抛出 "InvalidStateError
"
DOMException
。
continuePrimaryKey(key, primaryKey)
方法步骤如下:
-
如果 transaction 的 状态不是 激活, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 this 的 源或 有效对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 this 的 源不是索引,抛出 "
InvalidAccessError
"DOMException
。 -
如果 this 的 方向不是 "
next
" 或 "prev
", 抛出 "InvalidAccessError
"DOMException
。 -
如果 this 的 已取值标志为 false(游标正在迭代或已到末尾), 抛出 "
InvalidStateError
"DOMException
。 -
令 r 为用 key 转换为键的结果。异常重新抛出。
-
如果 r 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
令 key 为 r。
-
令 r 为用 primaryKey 转换为键的结果。异常重新抛出。
-
如果 r 为 "invalid value" 或 "invalid type",抛出 "
DataError
"DOMException
。 -
令 primaryKey 为 r。
-
如果 key 小于 this 的 位置,且 this 的 方向为 "
next
", 抛出 "DataError
"DOMException
。 -
如果 key 大于 this 的 位置,且 this 的 方向为 "
prev
", 抛出 "DataError
"DOMException
。 -
如果 key 等于 this 的 位置,且 primaryKey 小于或等于 this 的 对象存储区位置,且 this 的 方向为 "
next
", 抛出 "DataError
"DOMException
。 -
如果 key 等于 this 的 位置,且 primaryKey 大于或等于 this 的 对象存储区位置,且 this 的 方向为 "
prev
", 抛出 "DataError
"DOMException
。 -
设 request 的 已处理标志为 false。
-
设 request 的 完成标志为 false。
-
令 operation 为用 遍历游标,参数为 当前 Realm 记录、this、key 和 primaryKey。
注意:
在新的游标数据加载前多次调用此方法(如在同一个 onsuccess 回调里连续调用两次 continuePrimaryKey()
),第二次调用会因游标的已取值标志被设为
false 而抛出 "InvalidStateError
"
DOMException
。
update(value)
方法步骤如下:
-
如果 transaction 的 状态不是 激活, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 transaction 是只读事务,抛出 "
ReadOnlyError
"DOMException
。 -
如果 this 的 源或 有效对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 this 的 已取值标志为 false(游标正在迭代或已到末尾), 抛出 "
InvalidStateError
"DOMException
。 -
如果 this 的 仅键标志为 true,抛出 "
InvalidStateError
"DOMException
。 -
令 targetRealm 为用户代理定义的 Realm。
-
令 clone 为 value 在 transaction 期间于 targetRealm 的副本。异常重新抛出。
为什么要创建副本?
值存储时会序列化。此处视为副本,允许规范中其它算法将其视为 ECMAScript 值,但实现可优化,只要行为不可被观察到。 -
令 operation 为用 存储记录到对象存储区,参数为 this 的 有效对象存储区、clone、this 的 有效键 和 false。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意: 调用存储记录到对象存储区的结果是,如果游标移动后该记录已被删除,则会新建记录。
delete()
方法步骤如下:
-
如果 transaction 的 状态不是 激活, 抛出 "
TransactionInactiveError
"DOMException
。 -
如果 transaction 是只读事务,抛出 "
ReadOnlyError
"DOMException
。 -
如果 this 的 源或 有效对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 this 的 已取值标志为 false(游标正在迭代或已到末尾), 抛出 "
InvalidStateError
"DOMException
。 -
如果 this 的 仅键标志为 true,抛出 "
InvalidStateError
"DOMException
。 -
令 operation 为用 从对象存储区删除记录,参数为 this 的 有效对象存储区 和 this 的 有效键。
-
返回用 this 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
当游标的仅键标志为 false 时,也实现 IDBCursorWithValue
接口。
[Exposed =(Window ,Worker )]interface :
IDBCursorWithValue IDBCursor {readonly attribute any value ; };
value
的 getter 步骤为返回 this 当前的 值。
注意:
如果 value
返回对象,则在游标的值改变前,每次访问都是同一个对象实例。修改该对象会被所有访问该值的人看到,但不会影响数据库内容。
4.10. IDBTransaction
接口
事务 对象实现如下接口:
[Exposed =(Window ,Worker )]interface :
IDBTransaction EventTarget {readonly attribute DOMStringList objectStoreNames ;readonly attribute IDBTransactionMode mode ;readonly attribute IDBTransactionDurability durability ; [SameObject ]readonly attribute IDBDatabase db ;readonly attribute DOMException ?error ;IDBObjectStore objectStore (DOMString );
name undefined commit ();undefined abort (); // Event handlers:attribute EventHandler onabort ;attribute EventHandler oncomplete ;attribute EventHandler onerror ; };enum {
IDBTransactionMode ,
"readonly" ,
"readwrite" };
"versionchange"
- transaction .
objectStoreNames
- transaction .
mode
-
返回事务创建时的模式("
readonly
" 或 "readwrite
"), 或对于升级事务为 "versionchange
"。 - transaction .
durability
- transaction .
db
-
返回事务的连接。
- transaction .
error
-
如果事务被中止,返回提供原因的错误(
DOMException
)。
objectStoreNames
的 getter 步骤如下:
注意: 此属性每次返回的列表内容不会改变,但在升级事务期间,多次调用可能随对象存储区的创建和删除返回不同内容。
mode
的 getter 步骤为返回 this 的 模式。
durability
的 getter 步骤为返回 this 的 持久性提示。
durability
属性为本版本新增。
支持 Chrome 82、Edge 82、Firefox 126 和 Safari 15。
🚧
db
的 getter 步骤为返回 this 的 连接关联的数据库。
error
的 getter 步骤为返回 this 的 错误,如果没有则为 null。
注意:
如果该事务因请求失败被中止,则此错误与请求的错误相同。若因事件处理器未捕获异常中止,则为 "AbortError
"
DOMException
。若因提交时错误中止,则反映失败原因(如
QuotaExceededError
、
"ConstraintError
"
或
"UnknownError
"
DOMException
)。
- transaction .
objectStore
(name) -
返回事务范围内的
IDBObjectStore
。 - transaction .
abort()
-
中止事务。所有待处理请求将以"
AbortError
"DOMException
失败,所有对数据库的更改将被回滚。 - transaction .
commit()
-
尝试提交事务。所有待处理请求将允许完成,但不再接受新请求。可用于强制事务快速完成,无需等待待处理请求触发
success
事件再正常提交。如待处理请求失败(如约束错误),事务将中止。成功请求的
success
事件仍会触发,但事件处理器抛出异常不会导致事务中止。类似地,失败请求的error
事件仍会触发,但调用preventDefault()
不会阻止事务中止。
objectStore(name)
方法步骤如下:
-
如果 this 的 状态为已完成, 抛出 "
InvalidStateError
"DOMException
。 -
令 store 为 范围内名称为 name 的对象存储区,如无则 抛出 "
NotFoundError
"DOMException
。
注意:
在同一个 IDBTransaction
实例上用同名多次调用该方法,返回的是同一个 IDBObjectStore
实例。
注意:
返回的 IDBObjectStore
实例仅关联当前 IDBTransaction
。在不同
IDBTransaction
上调用会返回不同的 IDBObjectStore
实例。
abort()
方法步骤如下:
-
如果 this 的 状态为提交中或已完成, 抛出 "
InvalidStateError
"DOMException
。
commit()
方法的步骤如下:
-
如果 this 的 状态 不是 active,则 抛出一个 "
InvalidStateError
"DOMException
。
commit()
方法为本版本新增。
支持 Chrome 76、Edge 79、Firefox 74 和 Safari 15。
🚧
注意:
通常无需在 commit()
上主动调用。事务会在所有未完成请求满足且无新请求时自动提交。此调用可用于在无需等待未完成请求事件分发时提前启动提交过程。
onabort
属性是事件处理 IDL 属性,其 事件类型为 abort
。
oncomplete
属性是事件处理 IDL 属性,其 事件类型为 complete
。
onerror
属性是事件处理 IDL 属性,其 事件类型为 error
。
注意:
如需判断事务是否成功完成,
请监听 事务的 complete
事件,
而不是某个 请求的 success
事件,
因为 success
事件触发后事务仍可能失败。
5. 算法
5.1. 打开数据库连接
要用 storageKey(请求打开数据库的存储键)、数据库 name、数据库 version 和 request 打开数据库连接,请按以下步骤执行:
-
令 queue 为 storageKey 和 name 的连接队列。
-
将 request 添加到 queue。
-
等待 queue 中所有先前的请求处理完毕。
-
令 db 为 storageKey 中数据库中名称为 name 的数据库,否则为 null。
-
如果 version 未定义,则如果 db 为 null 设为 1,否则设为 db 的版本。
-
如果 db 为 null,令 db 为新建的数据库,名称为 name,版本为 0,且无对象存储区。如失败,返回相应错误(如
QuotaExceededError
或 "UnknownError
"DOMException
)。 -
如果 db 的版本大于 version,则返回新创建的 "
VersionError
"DOMException
,并终止步骤。 -
令 connection 为到 db 的新连接。
-
设 connection 的版本为 version。
-
如果 db 的版本小于 version,则:
-
令 openConnections 为所有与 db 相关但不包括 connection 的连接集合。
-
遍历 openConnections 中未设置待关闭标志为 true 的 entry,排队数据库任务以触发版本变更事件,事件名为
versionchange
,目标为 entry,参数为 db 的版本和 version。注意: 触发该事件可能导致 openConnections 中部分对象被关闭,此时不会对这些对象再次触发
versionchange
事件,即使还没触发过。 -
等待所有事件触发完毕。
-
如果 openConnections 中有连接仍未关闭,排队数据库任务以触发版本变更事件,事件名为
blocked
,目标为 request,参数为 db 的版本和 version。 -
用 connection、version 和 request运行 升级数据库。
-
如果 connection已关闭, 返回新创建的 "
AbortError
"DOMException
,并终止步骤。 -
如果 request 的 error 已设置,则使用 connection 执行 关闭数据库连接的步骤,返回一个新创建的 "
AbortError
"DOMException
,并中止这些步骤。
-
-
返回 connection。
5.2. 关闭数据库连接
要用 connection 对象和可选的 forced flag 关闭数据库连接,请按以下步骤执行:
注意: 一旦连接的待关闭标志设为 true,就不能再通过该连接创建新事务。所有会创建事务的方法会先检查连接的待关闭标志,如果为 true 就抛出异常。
注意: 一旦连接关闭,将会解除对升级数据库和删除数据库步骤的阻塞,这两者都等待所有连接关闭后继续。
5.3. 删除数据库
要用请求删除数据库的 storageKey、数据库 name 和 request 删除数据库,请按以下步骤执行:
-
令 queue 为 storageKey 和 name 的连接队列。
-
将 request 添加到 queue。
-
等待 queue 中所有先前的请求处理完毕。
-
令 db 为 storageKey 中名称为 name 的数据库,如不存在则返回 0。
-
令 openConnections 为所有与 db 关联的连接集合。
-
遍历 openConnections 中未设置待关闭标志为 true 的 entry,排队数据库任务以触发版本变更事件,事件名为
versionchange
,目标为 entry,参数为 db 的版本和 null。注意: 触发该事件可能导致 openConnections 中部分对象被关闭,此时不会对这些对象再次触发
versionchange
事件,即使还没触发过。 -
等待所有事件触发完毕。
-
如果 openConnections 中有连接仍未关闭,排队数据库任务以触发版本变更事件,事件名为
blocked
,目标为 request,参数为 db 的版本和 null。 -
令 version 为 db 的版本。
-
删除 db。如失败,返回相应错误(如
QuotaExceededError
或 "UnknownError
"DOMException
)。 -
返回 version。
5.4. 提交事务
要用待提交的 transaction 提交事务,请按以下步骤执行:
-
将 transaction 的 状态 设置为 committing。
-
并行运行以下步骤:
-
如果 transaction 的 状态不再是 committing,则终止这些步骤。
-
尝试将 transaction 所做的所有未完成更改写入 数据库,并考虑 transaction 的 durability hint。
-
如果将更改写入 数据库时发生错误,则对 transaction 及与错误类型相应的类型(例如
QuotaExceededError
或 "UnknownError
"DOMException
)执行 中止事务,并终止这些步骤。 -
排队一个数据库任务以运行以下步骤:
5.5. 中止事务
要使用要中止的 transaction 和 error来中止事务,请执行以下步骤:
-
事务对数据库所做的所有更改都会被撤销。对于升级事务,包括对对象仓库和索引集合的更改,以及版本的更改。在事务期间创建的任何对象仓库和索引现在在其他算法中被视为已删除。
-
将 transaction 的 error 设置为 error。
-
遍历transaction 的 请求列表中的每个 request,中止 异步执行请求的步骤,设置 request 的 processed flag 为 true,并排队一个数据库任务执行以下步骤:
-
将 request 的 done flag 设置为 true。
-
将 request 的 result 设置为 undefined。
-
将 request 的 error 设置为新创建的 "
AbortError
"DOMException
。 -
触发一个事件 名为
error
于 request ,其bubbles
和cancelable
属性初始化为 true。
-
-
排队一个数据库任务以执行以下步骤:
-
如果 transaction 是 升级事务,则将 transaction 的 连接所关联的 数据库的 升级事务设为 null。
-
如果 transaction 是 升级事务,则:
-
令 request 为与 transaction 关联的 open request。
-
将 request 的 transaction 设置为 null。
-
将 request 的 result 设置为 undefined。
-
将 request 的 processed flag 设置为 false。
-
将 request 的 done flag 设置为 false。
-
-
5.6. 异步执行请求
要用 source 对象和待在数据库上执行的 operation 以及可选的 request 异步执行请求,请按以下步骤执行:
如果创建的请求所属的 事务被中止,可随时终止本步骤,按中止事务流程处理。
-
令 transaction 为与 source 关联的 事务。
-
将 request 添加到 transaction 的 请求列表末尾。
-
并行运行以下步骤:
-
令 result 为执行 operation 的结果。
-
如果 result 是错误,且 transaction 的 状态为 committing,则对 transaction 和 result 执行 中止事务,并终止这些步骤。
-
如果 result 是错误,则回滚 operation 所做的所有更改。
注意:这只会回滚该请求所做的更改,不会影响事务所做的其他更改。
-
将 request 的 processed flag 设置为 true。
-
排队一个数据库任务以运行以下步骤:
-
返回 request。
5.7. 升级数据库
要用 connection(一个连接)、新 version 和 request 升级数据库,请按以下步骤执行:
-
令 db 为 connection 的 数据库。
-
将 db 的 升级事务设置为 transaction。
-
启动 transaction。
-
令 old version 为 db 的 版本。
-
将 request 的 processed flag 设置为 true。
-
排队一个数据库任务以执行以下步骤:
-
将 request 的 result 设置为 connection。
-
将 request 的 transaction 设置为 transaction。
-
将 request 的 done flag 设置为 true。
-
令 didThrow 为 触发 version change 事件(事件名为
upgradeneeded
,目标为 request,参数为 old version 和 version)的结果。 -
如果 transaction 的 状态为 active,则:
-
如果 didThrow 为 true,则对 transaction 和新创建的 "
AbortError
"DOMException
执行 中止事务。
-
-
等待 transaction 完成。
5.8. 中止升级事务
要用 transaction 中止升级事务,请按以下步骤执行:
注意: 此步骤在中止事务流程中按需运行,会回滚对数据库的变更,包括关联的对象存储区和索引集合,以及版本变更。
-
令 connection 为 transaction 的连接。
-
令 database 为 connection 的数据库。
-
若 database 之前存在,设 connection 的版本为 database 的版本;若新创建则为 0。
注意: 此操作会回滚
version
属性在IDBDatabase
对象上的值。 -
若 database 之前存在,设 connection 的对象存储区集合为 database 中的对象存储区集合;若新创建则设为空集合。
注意: 此操作会回滚
objectStoreNames
在IDBDatabase
对象上的值。 -
遍历与 transaction 关联的每个对象存储区句柄 handle,包括事务期间新建或删除的对象存储区的句柄:
注意: 此操作会回滚
name
和indexNames
在相关IDBObjectStore
对象上的值。此操作如何可观测?
虽然脚本无法在事务中止后通过objectStore()
方法访问对象存储区,但仍可持有IDBObjectStore
实例引用,并可查询name
和indexNames
属性。
注意:
即使被中止的 升级事务正在创建一个新的 数据库,IDBDatabase
实例的
name
属性不会被修改。
5.9. 触发成功事件
要在 request 上触发成功事件,请按以下步骤执行:
-
设 event 的
type
属性为 "success
"。 -
设 event 的
bubbles
和cancelable
属性为 false。 -
令 transaction 为 request 的事务。
-
令 legacyOutputDidListenersThrowFlag 初始为 false。
-
如果 transaction 的 状态为 inactive, 则将 transaction 的 状态设置为 active。
-
使用 legacyOutputDidListenersThrowFlag,在 request 上分发 event。
-
如果 transaction 的 状态为 active,则:
-
如果 legacyOutputDidListenersThrowFlag 为 true,则对 transaction 和新创建的 "
AbortError
"DOMException
执行 中止事务。
5.10. 触发错误事件
要在 request 上触发错误事件,请执行以下步骤:
-
将 event 的
type
属性设置为 "error
"。 -
将 event 的
bubbles
和cancelable
属性设置为 true。 -
令 transaction 为 request 的 事务。
-
令 legacyOutputDidListenersThrowFlag 初始值为 false。
-
如果 transaction 的 状态为 inactive,则将 transaction 的 状态设置为 active。
-
如果 transaction 的 状态为 active,则:
-
如果 legacyOutputDidListenersThrowFlag 为 true,则对 transaction 和新创建的 "
AbortError
"DOMException
执行 中止事务,并终止这些步骤。即使 event 的 canceled flag 为 false,也会执行此操作。注意: 这意味着如果错误事件被触发且事件处理器抛出异常,transaction 的
error
属性将被设置为一个AbortError
,而不是 request 的 error,即使preventDefault()
从未被调用。 -
如果 event 的 canceled flag 为 false,则对 transaction 和 request 的 error 执行 中止事务,并终止这些步骤。
5.11. 克隆值
要在 transaction 期间于 targetRealm 中对 value 进行克隆,请执行以下步骤:
5.12. 创建用于检索多个项的请求
要从 对象存储区或索引用 targetRealm、sourceHandle、kind、queryOrOptions 和可选的 count 创建用于检索多个项的请求,请按以下步骤执行:
-
令 source 为 sourceHandle 的索引或对象存储区。 若 sourceHandle 是索引句柄,则 source 是该句柄关联的索引; 否则 source 是该对象存储区句柄关联的对象存储区。
-
如果 source 已被删除,抛出 "
InvalidStateError
"DOMException
。 -
如果 source 是索引且其对象存储区已被删除,抛出 "
InvalidStateError
"DOMException
。 -
令 transaction 为 sourceHandle 的事务。
-
如果 transaction 的状态不是激活,抛出 "
TransactionInactiveError
"DOMException
。 -
令 range 为键范围。
-
令 direction 为游标方向。
-
如果用 queryOrOptions 运行 是否为潜在有效键范围 返回 true,则:
-
否则:
-
令 operation 为待运行的算法。
-
如果 source 是索引,则设 operation 为用 targetRealm、source、range、kind、direction 和(如给定)count运行 从索引检索多个项。
-
否则设 operation 为用 targetRealm、source、range、kind、direction 和(如给定)count运行 从对象存储区检索多个项。
-
返回用 sourceHandle 和 operation 运行 异步执行请求的结果(
IDBRequest
)。
注意:
range 可以是键或键范围(IDBKeyRange
),用于标识要检索的记录项。如果为 null
或未指定,则使用无限键范围。
如果指定了 count,且区间内记录数超过 count,则只检索前 count 项。
6. 数据库操作
本节描述在数据库中的对象存储区和索引上进行的各种操作。 这些操作由异步执行请求步骤运行。
注意: 下述操作步骤中的 StructuredDeserialize() 调用不会抛出异常(如 ! 前缀所示),因为它们只操作 StructuredSerializeForStorage() 的先前输出结果。
6.1. 对象存储区存储操作
要用 store、value、可选 key 和 no-overwrite flag 存储记录到对象存储区,请按以下步骤执行:
-
如果 store 使用键生成器:
-
如果 key 未定义:
-
令 key 为用 store 生成键的结果。
-
如果 key 失败,则本操作失败,错误为 "
ConstraintError
"DOMException
。算法终止。 -
如果 store 也使用内嵌键,则用 value、key 和 store 的键路径运行 通过键路径注入键到值。
-
-
否则,对 store 用 key运行 可能更新键生成器。
-
-
如果本步骤给定了 no-overwrite flag 且为 true,且 store 已存在键为 key 的记录,则操作失败,错误为 "
ConstraintError
"DOMException
。算法终止。 -
如果 store 已存在键为 key 的记录,则用 key 运行 从对象存储区删除记录。
-
在 store 存储一个记录,键为 key,值为 !StructuredSerializeForStorage(value)。记录存储在对象存储区的记录列表中,按键升序排序。
-
对于每个引用 store 的 index:
-
令 index key 为用 value、index 的键路径和multiEntry 标志运行 通过键路径从值提取键的结果。
-
如果 index key 为异常、无效或失败,则不再处理该 index,继续下一个索引。
注意: 本步骤异常不会重新抛出。
-
如果 index 的multiEntry 标志为 false,或 index key 不是数组键,且 index 已存在键为 index key 的记录,且 index 的唯一标志为 true,则操作失败,错误为 "
ConstraintError
"DOMException
。算法终止。 -
如果 index 的multiEntry 标志为 true 且 index key 是数组键,且 index 已存在键等于任一子键的记录,且 index 的唯一标志为 true,则操作失败,错误为 "
ConstraintError
"DOMException
。算法终止。 -
如果 index 的multiEntry 标志为 false,或 index key 不是数组键,则在 index 存储一个记录,键为 index key,值为 key。记录存储在 index 的记录列表中,主排序为记录键,次排序为记录值,均为升序。
-
如果 index 的multiEntry 标志为 true 且 index key 是数组键,则遍历 index key 的每个子键,在 index 存储一个记录,键为 subkey,值为 key。记录存储在 index 的记录列表中,主排序为记录键,次排序为记录值,均为升序。
注意: 子键可以为空,此时不会向索引添加记录。
-
-
返回 key。
6.2. 对象存储区检索操作
要用 targetRealm、store 和 range 从对象存储区检索值,请按以下步骤执行。返回 undefined、ECMAScript
值或错误(DOMException
):
-
如果未找到 record,返回 undefined。
-
令 serialized 为 record 的值。如底层存储读取该值时出错,返回新创建的 "
NotReadableError
"DOMException
。 -
返回 ! StructuredDeserialize(serialized, targetRealm)。
要用 store 和 range 从对象存储区检索键,请按以下步骤执行:
要用 targetRealm、store、range、kind、direction 和可选 count 从对象存储区检索多个项,请按以下步骤执行:
-
如果未给定 count 或为 0,则令 count 为无穷大。
-
令 records 为一个空的记录列表。
-
如果 direction 为 "
next
" 或 "nextunique
", 设 records 为 store 的记录列表中前 count 个其键在 range 内的记录。 -
如果 direction 为 "
prev
" 或 "prevunique
", 设 records 为 store 的记录列表中最后 count 个其键在 range 内的记录。 -
令 list 为一个空列表。
-
遍历 records 中的每个 record,根据 kind 分支:
- "key"
- "value"
-
-
令 serialized 为 record 的值。
-
令 value 为 ! StructuredDeserialize(serialized, targetRealm)。
-
将value添加到list。
-
- "record"
-
返回 list。
6.3. 索引检索操作
要用 targetRealm、index 和 range 从索引检索引用值,请按以下步骤执行:
要用 index 和 range 从索引检索值,请按以下步骤执行:
要用 targetRealm、index、range、kind、direction 和可选 count 从索引检索多个项,请按以下步骤执行:
6.4. 对象存储区删除操作
要用 store 和 range 从对象存储区删除记录,请按以下步骤执行:
6.5. 记录计数操作
要用 source 和 range 统计区间内的记录数,请按以下步骤执行:
-
令 count 为 source 的记录列表中键在 range 内的记录数量(如有)。
-
返回 count。
6.6. 对象存储区清空操作
6.7. 游标迭代操作
要用 targetRealm、cursor,可选 key 和 primaryKey 以及可选 count 迭代游标,请按以下步骤执行:
-
令 source 为 cursor 的源。
-
令 direction 为 cursor 的方向。
-
断言:如给定 primaryKey,则 source 为索引且 direction 为 "
next
" 或 "prev
"。 -
令 records 为 source 的记录列表。
注意: records 总是按升序键排序。 如 source 为索引,则 records 会按升序值做二级排序 (索引中的值为引用对象存储区记录的键)。
-
令 range 为 cursor 的区间。
-
令 position 为 cursor 的位置。
-
令 object store position 为 cursor 的对象存储区位置。
-
如未给定 count,令 count 为 1。
-
当 count 大于 0 时:
-
根据 direction 分支:
- "
next
" -
令 found record 为 records 中第一个满足所有以下条件的记录:
-
如 key 已定义:
-
记录键大于或等于 key。
-
-
如 primaryKey 已定义:
-
记录键等于 key 且记录值大于或等于 primaryKey
-
记录键大于 key。
-
-
如 position 已定义且 source 为对象存储区:
-
记录键大于 position。
-
-
如 position 已定义且 source 为索引:
-
记录键等于 position 且记录值大于 object store position
-
记录键大于 position。
-
-
记录键在 range 内。
-
- "
nextunique
" -
令 found record 为 records 中第一个满足所有以下条件的记录:
-
如 key 已定义:
-
记录键大于或等于 key。
-
-
如 position 已定义:
-
记录键大于 position。
-
-
记录键在 range 内。
-
- "
prev
" -
令 found record 为 records 中最后一个满足所有以下条件的记录:
-
如 key 已定义:
-
记录键小于或等于 key。
-
-
如 primaryKey 已定义:
-
记录键等于 key 且记录值小于或等于 primaryKey
-
记录键小于 key。
-
-
如 position 已定义且 source 为对象存储区:
-
记录键小于 position。
-
-
如 position 已定义且 source 为索引:
-
记录键等于 position 且记录值小于 object store position
-
记录键小于 position。
-
-
记录键在 range 内。
-
- "
prevunique
" -
令 temp record 为 records 中最后一个满足所有以下条件的记录:
-
如 key 已定义:
-
记录键小于或等于 key。
-
-
如 position 已定义:
-
记录键小于 position。
-
-
记录键在 range 内。
如 temp record 定义,则令 found record 为 records 中第一个键等于 temp record 键的记录。
注意: 用 "
prevunique
" 迭代访问的记录与 "nextunique
" 访问的记录相同,只是顺序相反。 -
- "
-
如 found record 未定义:
-
令 position 为 found record 的键。
-
如 source 为索引,令 object store position 为 found record 的值。
-
减少 count。
-
-
设 cursor 的位置为 position。
-
如 source 为索引,设 cursor 的对象存储区位置为 object store position。
-
设 cursor 的键为 found record 的键。
-
如 cursor 的仅键标志为 false:
-
令 serialized 为 found record 的值(如 source 为对象存储区),或 found record 的引用值(否则)。
-
设 cursor 的值为 ! StructuredDeserialize(serialized, targetRealm)
-
-
设 cursor 的已获取值标志为 true。
-
返回 cursor。
7. ECMAScript 绑定
本节定义了本规范中键值如何与 ECMAScript 值相互转换,以及如何通过键路径从 ECMAScript 值中提取或注入键。本节引用了 ECMAScript 语言规范中的类型和算法,并采用了一些算法约定。 [ECMA-262] 未在此详细说明的转换由 [WEBIDL] 定义。
7.1. 从值中提取键
要用 value、keyPath 和可选 multiEntry flag 通过键路径从值中提取键,请按以下步骤执行。结果可能为键、invalid、failure,或步骤可能抛出异常。
要用 value 和 keyPath 在值上求值键路径,请按以下步骤执行。结果可能为 ECMAScript 值或 failure,或步骤可能抛出异常。
-
如 keyPath 是字符串列表:
-
如 keyPath 是空字符串,返回 value 并跳过剩余步骤。
-
令 identifiers 为用 keyPath 按 U+002E FULL STOP 字符(.)严格分割的结果。
-
遍历 identifiers 中每个 identifier,跳转到下方合适步骤:
- 如Type(value)为 String 且
identifier为 "
length
" -
令 value 为 Number,等于 value 的元素数量。
- 如 value 为
Array
且 identifier 为 "length
" - 如 value 为
Blob
且 identifier 为 "size
" -
令 value 为 Number,等于 value 的
size
。 - 如 value 为
Blob
且 identifier 为 "type
" -
令 value 为 String,等于 value 的
type
。 - 如 value 为
File
且 identifier 为 "name
" -
令 value 为 String,等于 value 的
name
。 - 如 value 为
File
且 identifier 为 "lastModified
" -
令 value 为 Number,等于 value 的
lastModified
。 - 否则
-
-
如Type(value)不是 Object,返回 failure。
-
令 hop 为 ! HasOwnProperty(value, identifier)。
-
如 hop 为 false,返回 failure。
-
如 value 为 undefined,返回 failure。
-
- 如Type(value)为 String 且
identifier为 "
-
返回 value。
注意: 上述步骤之所以可以断言,是因为该算法仅应用于 StructuredDeserialize 的输出值,且只访问“自有”属性。
7.2. 向值中注入键
注意: 本节使用的键路径始终为字符串,绝不是序列,因为无法创建既有键生成器又有为序列的键路径的对象存储区。
要用 value 和 keyPath 检查键能否注入到值中,请按以下步骤执行。结果为 true 或 false。
注意: 上述步骤可断言,因为该算法只应用于 StructuredDeserialize 的输出值。
要用 value、key 和 keyPath 通过键路径注入键到值,请按以下步骤执行:
-
令 identifiers 为用 keyPath 按 U+002E FULL STOP 字符(.)严格分割的结果。
-
断言:identifiers 非空。
-
令 last 为 identifiers 的最后一个项,并将其从列表中移除。
-
遍历剩余 identifiers 中的每个 identifier:
-
令 hop 为 ! HasOwnProperty(value, identifier)。
-
如 hop 为 false:
-
令 o 为新建的
Object
,创建方法同({})
表达式。 -
令 status 为 CreateDataProperty(value, identifier, o)。
-
断言:status 为 true。
-
-
令 keyValue 为用 key 运行 键转换为值的结果。
-
令 status 为 CreateDataProperty(value, last, keyValue)。
-
断言:status 为 true。
注意: 上述步骤可断言,因为该算法只应用于 StructuredDeserialize 的输出值,且已运行 检查键能否注入到值中流程。
7.3. 键转换为值
要用 key 键转换为值,请按以下步骤执行。返回 ECMAScript 值。
7.4. 值转换为键
要用 ECMAScript 值 input 和可选的集合 seen 值转换为键,请按以下步骤执行。 结果可能为键、“invalid value”或“invalid type”,或步骤可能抛出异常。
-
如果未给定 seen,则令 seen 为新建的空集合。
-
如果 seen 包含 input,则返回 "invalid value"。
-
跳转到下方合适步骤:
- 如 Type(input) 为 Number
- 如 input 是
Date
(有 [[DateValue]] 内部槽) - 如 Type(input) 为 String
- 如 input 是缓冲区源类型
-
-
如 input 已分离,则返回 "invalid value"。
-
令 bytes 为用 input 获取缓冲区源字节副本的结果。
-
- 如 input 是数组异对象
-
-
将 input 添加到 seen。
-
令 keys 为新建的空列表。
-
令 index 为 0。
-
当 index 小于 len 时:
-
令 hop 为 ? HasOwnProperty(input, index)。
-
如 hop 为 false,返回 "invalid value"。
-
令 key 为用参数 entry 和 seen 运行 值转换为键的结果。
-
ReturnIfAbrupt(key)。
-
如 key 为 "invalid value" 或 "invalid type",终止本步骤并返回 "invalid value"。
-
将 key 添加到 keys。
-
增加 index。
-
- 否则
-
返回 "invalid type"。
要用 ECMAScript 值 input 值转换为多条目键,请按以下步骤执行。 结果可能为键、“invalid value”或“invalid type”,或步骤可能抛出异常。
注意:
这些步骤类似于值转换为键,
但如果顶层值为 Array
,
则无法转换为键的成员会被忽略,且会去除重复项。
例如,值 [10, 20, null, 30, 20]
会被转换为数组键,其子键为
10、20、30。
8. 隐私注意事项
本节为非规范性内容。
8.1. 用户追踪
第三方主机(或任何能够将内容分发到多个站点的对象)可以利用其客户端数据库中存储的唯一标识符,在多个会话间追踪用户,建立用户活动档案。结合某些了解用户真实身份的站点(如需要认证凭据的电商网站),这可能让压制性组织比纯匿名 Web 使用环境下更精准地定位个人。
有多种技术可用于降低用户追踪的风险:
- 阻止第三方存储
-
用户代理可以限制数据库对象的访问,仅允许顶层文档所属域的脚本访问,例如拒绝其它域在
iframe
页面中对 API 的访问。 - 让存储的数据过期
-
用户代理可在一段时间后自动删除已存储的数据。
这可以限制站点追踪用户的能力,因为站点只能在用户自己认证(如购买或登录服务)时跨多个会话追踪用户。
但这也使用户数据面临丢失风险。
- 将持久存储视为 Cookie
-
用户代理应以与 HTTP 会话 Cookie 强关联的方式向用户展示数据库功能。[COOKIES]
这可能促使用户用合理怀疑态度看待此类存储。
- 按站点对数据库访问进行白名单
-
用户代理可要求用户授权后,站点方能访问数据库。
- 第三方存储归属信息
-
如果这些信息用于呈现当前持久存储中的数据视图,用户可以据此做出明智决定,选择要清理哪些存储内容。结合黑名单(“删除此数据并阻止该域名再次存储数据”),用户可将持久存储仅限于信任的站点。
- 共享黑名单
-
用户代理可允许用户共享其持久存储域名黑名单。
这样社区可以协作保护隐私。
虽然上述建议可以防止此 API 被轻易用于用户追踪,但并不能完全阻止。在单一域名下,站点仍可在会话期间追踪用户,并将所有信息与任何识别信息(姓名、信用卡号、地址)一起传给第三方。如果第三方与多个站点合作获取这些信息,仍可建立用户档案。
然而,即使用户代理完全不配合,也可以一定程度上追踪用户,比如使用 URL 中的会话标识符,这一技术已被广泛用于无害目的,但同样可轻易用于用户追踪(甚至事后追踪)。这些信息可与其它站点共享,利用访客的 IP 地址及其它用户特定数据(如 user-agent 头和配置设置)把不同会话归为同一用户档案。
8.2. Cookie 恢复
如果持久存储的用户界面将本规范描述的持久存储功能与 HTTP 会话 Cookie 的数据分开展示,用户很可能只删除其中一处的数据。这将让站点把两项功能当作彼此的冗余备份,从而破坏用户保护隐私的努力。
8.3. 数据敏感性
用户代理应将持久存储的数据视为潜在敏感数据;此机制完全可能存储电子邮件、日历、健康记录或其它机密文档。
为此,用户代理应确保删除数据时,数据能被及时从底层存储中清除。
9. 安全注意事项
9.1. DNS 欺骗攻击
由于存在 DNS 欺骗攻击的可能,无法保证宣称属于某域名的主机确实来自该域。为降低这种风险,页面可使用 TLS。采用 TLS 的页面可确保只有使用 TLS 且证书标识为同一域的页面能访问其数据库。
9.2. 跨目录攻击
共享同一主机名的不同作者(如在 geocities.com
上托管内容的用户)都共享一套数据库。
没有按路径限制访问的功能。共享主机上的作者因此应避免使用这些功能,因为其它作者可以轻易读取或覆盖数据。
注意: 即使提供了路径限制功能,常规 DOM 脚本安全模型也能轻易绕过此保护,从任何路径访问数据。
9.3. 实现风险
实现这些持久存储功能的主要风险是让恶意站点读取其它域的数据,以及让恶意站点写入数据后被其它域读取。
让第三方站点读取本不应被其域访问的数据会造成信息泄露,例如某域上的用户购物心愿单可被其它域用于定向广告,或某文档编辑站点存储的用户机密文档被竞争公司站点查看。
让第三方站点向其它域的持久存储写入数据会导致信息伪造,同样危险。例如恶意站点可往用户心愿单添加记录,或将用户会话标识设置为恶意站点已知的 ID,以追踪用户在受害站点上的操作。
因此,严格遵循本规范描述的存储键分区模型对于用户安全至关重要。
如果用主机名或数据库名生成持久化到文件系统的路径,则必须进行适当的转义,以防攻击者用相对路径如 "../
" 访问其它存储键的信息。
9.4. 持久化风险
实际实现会将数据持久化到非易失性存储介质。数据存储时会被序列化,读取时反序列化,具体序列化格式由用户代理决定。用户代理可能会随时间更改其序列化格式,例如为支持新数据类型或提升性能。为满足本规范的操作要求,实现必须能以某种方式处理旧序列化格式。不正确处理旧数据会导致安全问题。除了基础序列化问题,序列化数据还可能包含在新版用户代理中无效的假设。
一个实际例子是 RegExp
类型。
StructuredSerializeForStorage 操作支持序列化 RegExp
对象。典型的用户代理会将正则表达式编译为机器指令,并假设输入数据的传递和结果的返回方式。如果这些内部状态作为数据存储的一部分被序列化,日后反序列化时可能出现各种问题。例如,数据的传递方式可能已变动。编译器输出中的安全漏洞可能已在用户代理更新中修复,但在序列化的内部状态中仍然存在。
用户代理必须正确识别和处理旧数据。一种做法是在序列化格式中加入版本标识符,在遇到旧数据时根据脚本可见状态重建任何内部状态。
10. 可访问性注意事项
本节为非规范性内容。
本规范描述的 API 在可访问性方面考虑有限:
-
它不提供内容的视觉渲染或颜色控制。
-
它不提供接受用户输入的功能。
-
它不提供用户交互功能。
-
它不定义文档语义。
-
它不提供基于时间的视觉媒体。
-
它不允许设置时间限制。
-
它不直接为终端用户提供内容,无论是文本、图形还是其它非文本形式。
-
它不定义传输协议。
该 API 允许存储结构化内容。文本内容可作为字符串存储。API 支持开发者存储如图片或音频等非文本内容,如 Blob
、
File
、
或 ImageData
对象。使用 API 创建动态内容应用的开发者应确保内容能被不同技术和需求的用户访问。
虽然 API 本身未定义具体机制,但结构化内容的存储也允许开发者存储国际化内容,通过不同记录或记录内结构保存语言版本。
API 未定义或要求用户代理生成用于与 API 交互的用户界面。用户代理可选提供支持 API 的界面元素。例如,当需要额外存储配额时弹窗提示用户、展示特定网站使用的存储量,或提供针对 API 存储的工具如检查、修改或删除记录。任何此类界面元素都必须考虑辅助工具。例如,以图形方式展示存储配额使用比例的界面,也必须向屏幕阅读器等工具提供同样数据。
11. 修订历史
本节为非规范性内容。
以下是自本规范上次发布以来的变更摘要。完整修订历史可在此处查阅。 第一版修订历史见该文档修订历史。 第二版修订历史见该文档修订历史。
-
清理 Indexed Database 事务算法现返回值以便与其它规范集成。(PR #232)
-
更新了partial interface 定义,因为
WindowOrWorkerGlobalScope
现在是mixin
。(PR #238) -
新增
databases()
方法。(issue #31) -
新增
commit()
方法。(issue #234) -
新增
request
属性。(issue #255) -
移除非标准
lastModifiedDate
属性的处理(针对File
对象)。(issue #215) -
移除
includes()
方法的支持。(issue #294) -
限制数组键为数组异对象(即禁止代理对象)。(issue #309)
-
事务在克隆操作期间会临时变为非激活状态。(PR #310)
-
新增
durability
选项和durability
属性。(issue #50) -
更精确地规定了§ 2.7.2 事务调度,禁止在有重叠范围的只读事务运行时启动读写事务。(issue #253)
-
新增可访问性注意事项章节。(issue #327)
-
采用 [infra] 的列表排序定义。(issue #346)
-
新增活跃事务定义,并将“运行升级事务”重命名为升级数据库以避免歧义。(issue #408)
-
规定了在 § 6.2 对象存储区检索操作中底层存储读取值失败时使用
DOMException
类型。(issue #423) -
更新值转换为键以对分离的 array buffer 返回 invalid。(issue #417)
-
更新
open()
以设置其请求的 已处理标志为 true。(issue #434) -
不包含尚未创建完成的数据库于
databases()
。(issue #442) -
澄清只有非激活的事务才可尝试自动提交。(issue #436)
-
修正升级数据库步骤以处理事务中止。(issue #436)
-
更新迭代游标的值序列化,针对对象存储区使用 值而非引用值。(issue #452)
-
为源句柄添加到游标以避免脚本访问内部索引和对象存储区。(issue #445)
-
定义了数据库任务队列,并用其替换任务队列(issue #421)
-
为
databases
() 补充了缺失的并行步骤(issue #421) -
澄清游标迭代谓词(issue #450)
-
为
getAllRecords(options)
方法添加到IDBObjectStore
和IDBIndex
。(issue #206) -
为
getAll()
和getAllKeys()
添加方向选项,适用于IDBObjectStore
和IDBIndex
(issue #130) -
QuotaExceededError
的用法更新为反映其现在是DOMException
派生接口而非异常名称。(issue #463) -
说明 null 对于error是有效的,并允许在中止事务时设置(issue #433)
-
移除
abort()
中多余的事务状态变更。
12. 致谢
本节为非规范性内容。
特别感谢 Nikunj Mehta,第一版的原作者,以及 Jonas Sicking、Eliot Graff、Andrei Popescu 和 Jeremy Orlow,第一版的其他编辑。
Garret Swart 在本规范设计中影响深远。
感谢 Tab Atkins, Jr. 创建并维护 Bikeshed 规范编辑工具,并提供整体写作建议。
特别感谢: Abhishek Shanthkumar、 Adam Klein、 Addison Phillips、 Adrienne Walker、 Alec Flett、 Andrea Marchesini、 Andreas Butler、 Andrew Sutherland、 Anne van Kesteren、 Anthony Ramine、 Ari Chivukula、 Arun Ranganathan、 Ben Dilts、 Ben Turner、 Bevis Tseng、 Boris Zbarsky、 Brett Zamir、 Chris Anderson、 Dana Florescu、 Danillo Paiva、 David Grogan、 Domenic Denicola、 Dominique Hazael-Massieux、 Evan Stade、 Glenn Maynard、 Hans Wennborg、 Isiah Meadows、 Israel Hilerio、 Jake Archibald、 Jake Drew、 Jerome Hode、 Josh Matthews、 João Eiras、 Kagami Sascha Rosylight、 Kang-Hao Lu、 Kris Zyp、 Kristof Degrave、 Kyaw Tun、 Kyle Huey、 Laxminarayan G Kamath A、 Maciej Stachowiak、 Marcos Cáceres、 Margo Seltzer、 Marijn Kruisselbrink、 Ms2ger、 Odin Omdal、 Olli Pettay、 Pablo Castro、 Philip Jägenstedt、 Shawn Wilsher、 Simon Pieters、 Steffen Larssen、 Steve Becker、 Tobie Langel、 Victor Costan、 Xiaoqian Wu、 Yannic Bonenberger、 Yaron Tausky、 Yonathan Randolph、 Zhiqiang Zhang, 所有上述人员的反馈和建议都推动了本规范的改进。