W3C Indexed Database API

html5中,新增的本地存储的方案有Web Storage、Web Sql Database API、Indexed Database API。


功能上:

Web Storage是一个基于key/value的简单API。value只能存储字符串类型的数据。无法应对复杂的web应用场景。

Web Sql和IndexedDB,都可以存储结构化数据。支持事务、索引。可以应对较为复杂的离线应用场景。

其中Web Sql的操作基于SQL语言。实现了Web Sql的浏览器,底层都是选用Sqlite作为SQL引擎。使用SQL的可以让开发人员从数据存储结构的设计上解脱出来,应用SQL丰富的语法特性,可以直接进行复杂的数据库操作。

IndexedDB的操作直接基于javascript。应用较为低级的API。可以直接透过索引、游标操作数据库中的数据。


标准化和实现:

Web Storage已经被W3C标准化,并被绝大多数现代浏览器实现。

Web Sql API曾经一度作为W3C的工作草案。但之后被废弃。W3C给出的理由是:

This document was on the W3C Recommendation track but specification work has stopped. The specification reached an impasse: all interested implementors have used the same SQL backend (Sqlite), but we need multiple independent implementations to proceed along a standardisation path.

这份规范遇到了窘境。所有的Web Sql实现厂商,都采用了Sqlite作为后端的SQL执行引擎。但这份规范在标准化的道路上需要若干独立的实现来推进。因此Web应用程序工作小组决定不再维护这份规范说明书,并且今后也不再打算重启这份规范说明书的维护工作。

听起来,W3C貌似不太喜欢Sqlite。而今天今天我主要说的Indexed Database API取而代之。成为W3C Web存储方案的候选建议。
Web Sql API的主要实现者是Chrome、Safari、Opera、Android、IOS、BB。IE和FF都不支持Web Sql API。
IndexedDB由于受到W3C的推崇。浏览器厂商的实现情况要好一些。但由于目前规范说明书还只是处于候选建议阶段。各大浏览器厂商目前的实现还有一些差异化。

通过Mozilla的一篇文章Beyond HTML5: Database APIs and the Road to IndexedDB,或许可以看出为何W3C和一些浏览器厂商对Web Sql不太感冒。或许他们认为。作为web 存储的基础API。SQL的方式有点起点太高了。应该制定一个更偏向底层的存储机制。然后后在它的基础上扩展出其他的形式。API还是应该偏向底层一些,核心一些。让广阔的开源社区有更大的发挥余地。当然。许多开发者也并不同意,他们认为sql目前看来,是最成熟、健壮的存储方案。Android,ios都在采用sqlite作为本地存储,为何浏览器上不可以?更何况还有学习成本的问题。SQL语言有强大的数据集理论支持。认同度高。基本上大家都习惯了SQL去操作数据库。但IndexedDB的API晦涩难懂。要实现一个复杂的查询本来只需要一条sql语句。而现在要自己在索引中打开游标,然后在程序里遍历。

但不管怎样。W3C能如此认同IndexedDB。肯定是它的理由的。而且nosql的存储方案相比起sql来说,也更加灵活。新技术的推广都要经过一段时间的考验和精炼。而对我们来说。Indexed Database无疑是我们终将要面对的。与其在这里抱怨。不如掌握好它,说不定在使用的过程中,我们会有新的心得发现。

IndexedDB现在网上的资料并不是很多。MDN和MSDN上有各自实现版本的API文档。demo也不多。但概念性的东西,还是要看W3C的规范说明书。API文档和demo里面都说的很简单。许多人还是在stackoverflow上问。IndexedDB如何多条件查询。如何批量插入,删除。如何orderby。等等这样的问题。我自己在使用的过程中,也稀里糊涂的走了一些弯路。规范还是要好好看的。照葫芦画瓢是很好的快速上手方法,不过最终要搞懂,还是需要把基础的东西搞的明白才行。因而打算借此机会,把W3C Indexed Database API的规范研读一下,顺便翻译出来。

3. Indexed Database API

3.1 概念

3.1.1 数据库

一个数据库的,跟文档或者worker是相同的。每个都有相关联的数据库

注意

设置document.domain不会影响数据库的

每个都有相关联的数据库一个数据库由一个或者多个对象存储空间组成,对象存储空间保存着数据库中存储的数据。

在指定的中,每个数据库都有一个名字来唯一标识。名字可以是任何字符串,包括空串,并且在数据库的存续期间保持约束。每个数据库都有一个当前的版本号. 在数据库被第一次创建时, 它的版本号是0。

注意

实现必须支持所有名字字符串。如果一个实现所采用的存储机制不能处理任意的数据库名字, 那么该实现必须使用转义或者类似的的方式映射到另外一个可以处理的名字。

注意

每个数据库在某一时刻只能有一个版本号;多个版本号不能共存。更改版本号的唯一途径是使用"versionchange" 事务

在删除阶段,数据库有一个删除挂起的标记位。当一个数据将要被删除时,标记位会被设置成true并且所有打开数据库的尝试都会阻塞直到数据库被删除。

打开数据库的操作会创建一个连接。在任意给定的时刻,可能有多个连接建立到给定的数据库。每个连接都有一个关闭挂起的标记,默认值是false。

当一个连接刚建立时,它处于打开状态。连接可以通过几种方式关闭。 如果连接被垃圾回收或者建立连接的执行上下文被销毁(例如用户离开当前页),连接就会被关闭。连接也可以通过关闭一个数据库连接的步骤被显示地关闭。当链接被关闭时删除挂起标记会被设置为真。

IDBDatabase表示一个数据库上的一个连接

3.1.2 对象存储空间

对象存储空间数据库中存储数据的主要存储方式。每个数据库都有一个对象存储空间集合。对象存储空间集合可以被改变,但只可以通过"versionchange"事务, 例如在一个upgradeneeded事件当中。当数据库刚被创建时,不存在任何对象存储空间

对象存储空间中有一个记录表保存着对象存储空间中的数据。每条记录有一个。线性表是按照键升序排列的。在给定的对象存储空间中永远不会存在多条记录有相同键的情况。

每个对象存储空间都有一个名字。名字在所属的数据库中是唯一的。每个对象存储空间可以有一个可选的键生成器和一个可选的键路径。如果对象存储空间有键路径,那么就说它使用内联键。否则就说它使用外联键

对象存储空间的可以有三种来源:

  1. 键生成器。 当需要键时,键生成器会每次产生一个单调递增的数字。
  2. 键可以通过键路径得到。
  3. 键也可以在向对象存储空间中存储的时候显示地设置。

IDBObjectStore接口表示一个对象存储空间。表示同一个对象存储空间的多个实例可以同时存在。

3.1.3 

为了能够高效地从索引数据库中获取记录, 每条record都会根据它的进行组织。如果说一个值是一个有效键则它必须遵循ECMAScript[ECMA-262]的类型Number 原始类型, String 原始类型, Date 对象, 或者 Array 对象。一个Array要满足有效键的条件,则必须数组中的每个元素都是已定义并且满足有效键的条件(例如稀疏数组不是有效键),并且Array 不能直接或者间接地引用自身。Array上的非数字属性都会被忽略,但这不会影响这个Array是否是有效键如果值是Number类型,则它必须不是NaN才可以是有效键。如果值是Date类型,它的内部属性,由[ECMA-262]定义的[[原始值]]不能是NaN遵循规范的用户代理必须支持所有有效键作为键。

注意

无穷Number数值是有效键。空Array也是。

需要接受作为输入参数的操作必须使每个参数的值,按顺序地通过结构化克隆算法[HTML5]复制,然后把复制的结果输入,再进行之后的操作。

注意

这个暗含的复制步骤确保了值在操作后不会因为ECMAScript [ECMA-262]的getters, setters和类型转换方法包括toString() 和 valueOf() 而发生改变。

为了方便比较, 所有的Array比所有的String,DateNumber都要大; 所有的String比所有的DateNumber大;所有的Date比所有的Number大。Number的值按照数字顺序比较.Date的值按照时间顺序比较。String通过章节11.8.5第四步描述的算法(The Abstract Relational Comparison Algorithm, of the ECMAScript Language Specification [ECMA-262])进行比较。Array通过下列方式比较:

  1. 让A成为第一个Array ,让B成为第二个Array
  2. 让变量length为A和B的长度中的较小者。
  3. 让变量i是0。
  4. 如果A的第i个元素小于B的第i个元素, 则A小于B。 跳过剩余的步骤。
  5. 如果A的第i个元素大于B的第i个元素, 则A大于B。跳过剩余的步骤。
  6. i自增1。
  7. 如果i不等于length,回到步骤4。否则继续执行下一步。
  8. 如果A的长度小于B的长度,ength,A小于B。如果A的长度大于B的长度, 则A大于B。否则A和B相等。
注意

Array中包含其他Array是可以的。上述算法对Array中的每个元素都会递归地进行。

注意

基于以上规则, 负无穷是的最小值。不存在最大值,因为Array有任何构成可能,最大的跟上一个其他的有效键会更大。

单词大于,小于 and 等于 已经在上述的比较中定义。

下面的例子举例说明了当使用内联键生成器来存储对象到对象存储空间时的不同行为。

例子 2

如果下列的条件为真:

那么键生成器生成的数值就会用来填入键值。 在下面的例子中对象存储空间的键路径是 "foo.bar"。而实际的对象中bar属性上并没有定义值 { foo: {} }。 当对象存储到对象存储空间时,bar属性就会被设置为4,因为这是对象存储空间应该生成的下一个

"foo.bar"
{ foo: {} }

如果以下的条件为真:

那么跟键路径关联的属性值就会被使用。自动生成的不会被使用。 在下面的例子中对象存储空间键路径"foo.bar"。真正对象上的bar属性的值是10, { foo: { bar: 10} }。 当对象存储到对象存储空间bar属性的值仍为10,因为这就是它的键值。

"foo.bar"
{ foo: { bar: 10 } }

下面的例子描述了这么一个场景。当指定的内联通过键路径定义,但没有对应的属性与之匹配。那么键生成器提供的值就会被用来填入键值,并且由系统负责创建对象的层级关系链。在下面例子中对象存储空间键路径"foo.bar.baz"。真正的对象上并没有foo属性,{ zip: {} }。当对象存储到对象存储空间时。foobar, 和baz 属性被依次分别地创建为前一个属性的子对象,直到foo.bar.baz可以被设置为止。foo.bar.baz就是对象存储空间的下一个键值。

"foo.bar.baz"
{ zip: {} }

试图在原始值上设置属性会失败并抛出错误。在下面的第一个例子中对象存储空间的键路径"foo"。实际的对象是4这个原始值。尝试在这个原始值上定义一个属性会失败。对数组来说也一样,数组上不能定义属性。 在下面的第二个例子中, 实际的对象是一个数组, [10]. 尝试在其上定义一个属性也会失败。

// 键生成器会尝试创建并存储键路径对应的属性到这个原始值上。
"foo"
4

// 键生成器会尝试创建并存储键路径对应的属性到这个数组上。
"foo"
[10]
3.1.4 

每条记录都会关联一个遵循规范的用户代理必须支持任意ECMAScript [ECMA-262] 的值,并被结构化克隆算法[HTML5]支持。这支持简单类型比如 String 原始值和 Date 对象 也支持 Object 和 Array 实例, File 对象, Blob 对象, ImageData对象等等。记录的通过值传递的方式存储和获取,而不是通过引用传递的方式; 之后对值的改变不会影响存储在数据库中的记录。

3.1.5 键路径

键路径 是一个 DOMString 或者 sequence<DOMString> 定义了如何从中提取出有效的键路径 是下列的一种:

  • 空的 DOMString。
  • 一个标识符,符合ECMAScript Language Specification [ECMA-262]制定的标识符名称DOMString
  • 一个由两个或者多个标识符组成的由点号分隔的DOMString (ASCII码是46的字符)。
  • 一个非空的sequence<DOMString> 只包含符合以上要求的DOMString
注意

键路径中不允许空格。

求键路径的值, 参照 使用键路径从值中提取键的步骤

键路径的值只可以被通过结构化克隆算法明确复制的属性访问。下列的属性也可以:

  • Blob.size
  • Blob.type
  • File.name
  • File.lastModifiedDate
  • Array.length
  • String.length
3.1.6 索引

有时比起通过去获取对象存储空间中的记录还有更好的方式。 索引 允许 通过对象存储空间记录中的属性来查询对象存储空间的某条记录

索引是特殊化的持久化键-值存储方式,它有一个引用 对象存储空间。索引有一个保存着索引中数据的记录表。当引用的对象存储空间中有记录被插入时,索引中记录项是被自动填充的。当引用的对象存储空间有插入、更新或者删除操作时,索引中的记录也是同步自动更新的。同一个对象存储空间可以被若干个索引所引用,这个对象存储空间的变动会触发所有这些索引的更新。

索引中记录的值同样是索引引用的对象存储空间的的值。键是通过键路径从引用的对象存储空间的值上得到的。如果一条给定记录有键X在索引中有值A,对索引键路径A上取值得到Y,那么索引会包含一条记录,它的键是Y,值是X

例 3
举例说明, 如果索引 引用的的对象存储空间包含一挑记录,它的键是 123,值是 { first: "Alice", last: "Smith" },并且 索引键路径"first",那么索引会包含这么一条记录,它的键是 "Alice",值是 123

索引中记录可以称之为有一个引用值。这是索引所引用的对象存储空间记录的值,该记录的键等于索引中记录的值。 因此在上面的例子中,索引中的键Y,值是X 的记录有一个引用值A。

例 4
在前面的例子中,索引中的记录有键 "Alice" 和值  123,会有一个 引用值  { first: "Alice", last: "Smith" }
注意

索引中的每条记录会并且只会引用索引引用的的对象存储空间中的一条记录。然而一个索引中可以有多条记录引用对象存储空间中的同一条记录,也可以在索引中找不到跟对象存储空间中给定记录相对应的记录。

索引中的记录总是按照记录的键排序的。然而不像对象存储空间一样,一个给定的索引可以有相同键的多条记录。这样的记录又会按照索引记录的值排序。(意思是对象存储空间中记录的主键)。

索引都有一个 名字。名字在引用的的对象存储空间中是唯一的。

每个索引页都有一个 unique 标记位。当这个标记位是真的时候,索引会强制任意两条记录都不能有相同的键。如果索引引用的对象存储空间中的一条记录试图插入或修改,从而导致在新的值上对索引的键路径取值的结果已经在索引中已经存在,那么试图的修改动作会产生失败。

每个所以也都有一个 multiEntry 标记位。这个标记位会影响当对索引的键路径取值的结果是一个数组时的行为。如果multiEntry 标记位置为假,那么一条是一个数组记录就会被添加到索引中。如果multiEntry 标记位置为真,那么数组中的每个条目都会被添加到索引中作为一条记录。每条记录的就是对应数组中每个条目的值。

IDBIndex 接口提供了访问索引的元数据的方法。注意表示同一个索引的这些接口多个实例是可以存在的。

3.1.7 事务

事务是被用来跟数据库中的数据相互作用的。当数据被读取或写入的时候,他是使用事务来完成的。

所有的事务都是通过一个连接建立的,这就是事务的连接。事务有一个 mode(模式)决定了哪种作用类型会被应用到事务上。 mode是在事务创建的时候指定的,在整个事务的生命周期内都维持不变。事务也有一个scope(范围) ,它界定了哪些对象存储空间可能会参与事务。事务有一个active 标记位,它决定了是否新的requests(请求)可以被发起。最后,事务都包含一个requests请求列表 。

每个事务都有一个固定的作用域,决定了事务何时被创建。事务的作用域在整个事务的生命周期内保持不变。

事务提供了一些对应用和系统错误的保护。一个事务可能被用来存储多条数据记录或者有条件地修改特定的数据记录。一个事务表示了一个原子性的和持续的一系列数据访问操作以及数据变更操作。

事务预期是短暂存续的。这被下面所述的原子性提交特性所支持。开发者依然可以让事务执行较长时间。然而,我们不建议使用这种方式,因为它可能导致糟糕的用户体验。

下面描述了事务生命周期

  1. 事务是通过IDBDatabase.transaction创建 的。传入的参数决定了事务的范围以及事务是否是只读的,当事务创建时,它的 active 标记位初始为true。
  2. 实现必须允许在事务中设置requests,只要它的active标记位是true。即使事务还没有开启的时候也是一样的。直到事务开启之前,实现不可以执行这些请求。然而,实现必须记录requests和他们的顺序。请求只能在事务处于活跃状态时进行设置。当试图对一个处于非活跃状态的事务设置一个请求时,实现必须拒绝并抛出一个TransactionInactiveError类型的DOMException异常。
  3. 一旦实现能按照下面的定义来约束给定的事务模式,实现必须建立一个队列来异步地启动这个事务。发生的时间点受以下的影响:
  4. 一旦事务开启,实现可以开始执行事务中的requests。除非另外有定义,请求必须按照它们设置的顺序依次执行。同样地,执行的结果也必须按照设置它们的顺序依次返回。不会保证不同事务间请求的返回次序。类似地,事务的模式应当保证不同事务中的两个请求可以以任意的次序执行,而不会影响最终的数据库存储的数据的结果。
  5. 事务可以在它结束以前,在任意时刻被终止,即使事务不处于活跃状态或还没有开启。当时事务被终止时,实现必须取消(回滚)事务期间对数据库的所有变动。这包括所有对象存储空间的变动和对对象存储空间索引的增加和删除。
  6. 事务可以由于非特定IDBRequest之外的原因而失败。例如由于提交事务时产生的输入输出错误,或者达到的存储配额上限而导致实现不能继续执行一个请求。在这种情况下实现必须执行事务退出步骤,使用当前的事务对象作为transaction,适当的错误类型对象作为error。例如如果存储配额达到上限,那么QuotaExceededError应当被作为error抛出,如果输入输出错误发生,UnknownError就应当被作为error抛出。
  7. 当事务不能继续处于活跃状态,只要事务还没有被终止,实现必须试图提交它。这通常发生在所有设置的请求都被执行并且他们的返回都被处理之后,并且没有新的请求再被设置的情况下。当事务被提交时,实现必须原子性地写入所有操作产生的数据库变更。也就是说,要么所有的变更必须都被写入,要么发生了错误,比如磁盘写入错误,实现就不可以写入任何数据库变更。如果这种错误发生,实现必须通过事务退出步骤终止事务,否则它就必须按照事务提交步骤来提交
  8. 当事务被提交或者终止,它就结束了。如果事务不能完成,比如因为实现发生了崩溃,或者由于用户明确地取消了事务,实现必须终止这个事务。

事务可以用下三种模式之一开启。模式决定了并发的对象存储空间访问之间是如何被隔离的。

enum IDBTransactionMode {
    "readonly",
    "readwrite",
    "versionchange"
};
枚举描述
readonly 该模式意味着事务只被允许读取数据。这种类型下的事务不能有任何数据库更改操作。这带来的好处是若干个"readonly"的事务可以同时进行,即使他们的范围是重叠的。例如,如果他们使用了相同的对象存储空间。只要数据库已经通过IDBDatabase.transaction方法打开,这种类型的事务就可以在任意时间创建。
readwrite 该模式下的事务可以读取、修改、删除对象存储空间中的数据。但是对象存储空间和索引不能被添加和删除。多个"readwrite"的事务如果范围是重叠的,他们就不能同时进行,因为那样可能在事务过程中会修改彼此之间数据。这种类型的事务就可以在任意时间创建。
versionchange 该模式跟"readwrite"是相似的,但是它可以额外地创建和移除对象存储空间和索引。这是唯一一种可以进行这种操作的事务类型。这种类型的事务不能被手动创建,去二代中它可以在upgradeneeded事件中被自动的创建。

"readonly" 模式下打开的任意数量的事务允许并发的进行,即使事务的范围是重叠的以及包含了相同的对象存储空间。只要"readonly" 事务正在进行,实现在通过事务创建的requests取得返回数据的时候必须保持原来的数据不变。也就是说,两个读取相同片段数据的请求必须产生相同的返回值,不管是否找到了需要返回的数据结果。

有许多方法可以让实现保证这些。实现可以阻止任何范围重叠的"readwrite"事务,直到 "readonly"的事务结束。或者实现可以允许 "readonly" 的事务观察一个在事务开启时对象存储空间的快照内容。

相似地,实现必须确保一个"readwrite"的事务只能受到事务自身产生的变动影响。例如,实现必须确保另外一个事务不会修改当前事务范围中的数据。实现必须也确保如果"readwrite"的事务成功完成,提交事务的变更到数据库不需要合并冲突。实现不可以由于合并冲突而终止事务。

如果多个"readwrite" 事务试图访问相同的对象存储空间(比如他们有重叠的范围),首先创建的事务必须优先访问对象存储空间。由于上一段中的要求,这也意味着该事务是在它结束之前,唯一能够访问这个对象存储空间的事务。

注意

一般来讲,上述的要求表示"readwrite"的事务,如果有重叠的范围的话,就总是按照他们创建的次序执行,而不会并行进行。

"versionchange"事务从不会跟其他事务并发的进行。当数据库打开时版本号高于当前,新的"versionchange"事务就会被自动创建,并且可以在upgradeneeded事件中得到事务对象。upgradeneeded事件不触发,"versionchange"事务就不会开启,直到所有其他的到同一个数据库连接关闭。这保证了所有其他的事务已经结束

只要"versionchange"事务正在进行,所有试图打开更多到同一个数据库连接动作将会被延迟,并且任何试图使用同一个连接去开启额外的事务都会导致抛出异常。这样"versionchange"事务不仅保证了没有其他事务并发地进行,也保证了只要事务正在进行,就没有其他事务会进入同一个数据库的等待队列。

注意

"versionchange"事务是当提供的数据库版本号大于当前的数据库版本号时被自动创建的。这个事务将会在upgradeneeded 时间处理函数中处于活跃状态,并且允许创建新的对象存储空间索引

用户代理必须确保公平合理的事务级别来避免产生死等。例如,如果多个"readonly"事务一个接一个地开启,实现不可以无限期地阻塞"readwrite"事务开启

事务对象实现自IDBTransaction接口。

3.1.8 请求

每个对数据库的读写操作都是通过request(请求)。每个请求代表一次读或写操作。Requests有一个done标志位,初始值是false,并且有一个source对象。每个请求也有一个result和一个error属性,他们都是不能访问的,知道done标志位被设置为true。

最后,请求有一个请求事务。 当请求被创建时,它总是通过异步执行请求步骤设置为一个事务对象。步骤设置请求事务为那个事务对象。步骤不会设置请求事务属于那个请求,因为请求会从IDBFactory.open方法作为返回值返回。但是那个方法创建的请求有一个null的请求事务

enum IDBRequestReadyState {
    "pending",
    "done"
};
枚举描述
pending 请求done标记位是false。
done 请求done标记位是true。
3.1.9 键范围

记录可以通过或者键范围的方式从对象存储空间中获取值。键范围 是对应键的数据类型上的一块连续的区间。一个键范围可能被限定下界或上界(有一个值分别对应比它还大或还小的所有元素)。 一个键范围如果同时限定了下界和上界,就说它是有界的。一个键范围既没有限定下界,也没有限定上界,就说它是无界的。一个键范围可能是打开的(键范围不包括它的终点)或者是闭合的(键范围包含它的终点)。键范围可能由单一数值组成。

IDBKeyRange 接口定义了键范围

interface IDBKeyRange {
    readonly    attribute any     lower;
    readonly    attribute any     upper;
    readonly    attribute boolean lowerOpen;
    readonly    attribute boolean upperOpen;
    static IDBKeyRange only (any value);
    static IDBKeyRange lowerBound (any lower, optional boolean open);
    static IDBKeyRange upperBound (any upper, optional boolean open);
    static IDBKeyRange bound (any lower, any upper, optional boolean lowerOpen, optional boolean upperOpen);
};
lower    any类型,只读
键范围的下界。
lowerOpen  boolean类型,只读
如果 键范围的下界是闭合的返回false,反之返回true。
upper    any类型,只读
键范围的上界。
upperOpen  boolean类型,只读
键范围的上界是闭合的返回false,反之返回true。

bound, static

创建并返回一个新的 键范围下界设置为 lower下界打开设置为 lowerOpen, 上界 设置为 upper, 上界打开设置为 upperOpen。

如果lower参数或者upper参数不是有效键,或者下界大于上界,或者上下界相同并且都是打开的情况下,实现必须抛出DataError类型的DOMException异常。

参数 类型 可空 可选 描述
lower any 下界值
upper any 上界值
lowerOpen boolean 下界闭合时设置为false,反之为true,默认是false。
upperOpen boolean 上界闭合时设置为false,反之为true,默认是false。
返回类型:  IDBKeyRange
lowerBound, static
创建并返回一个新的 键范围下界设置为 lower下界打开设置为 open,upperundefined,upperOpen为true。如果 lower参数 不是 有效键,实现 必须抛出 DataError类型的 DOMException异常。
参数 类型 可空 可选 描述
lower any 下界值
open boolean 下界闭合时设置为false,反之为true,默认是false。
返回类型:  IDBKeyRange
only, static
创建并返回一个新的 键范围lowerupper都设置为 value。并且 lowerOpenupperOpen都设置为false。如果 upper参数 不是 有效键,实现 必须抛出 DataError类型的 DOMException异常。

参数 类型 可空 可选 描述
lower any 下界值

返回类型:  IDBKeyRange
upperBound, static

创建并返回一个新的键范围下界设置为undefined下界打开设置为true,upper为upper,upperOpen为open。如果upper参数不是有效键,实现必须抛出DataError类型的DOMException异常。

参数 类型 可空 可选 描述
upper any 上界值
open boolean 上界闭合时设置为false,反之为true,默认是false。

返回类型:  IDBKeyRange

一个键范围中必须同时满足下列条件:

3.1.10 游标

游标是一个在数据库中迭代多条数据的透明机制。存储操作时在相应的索引或者对象存储空间上执行的。游标 包含了索引或者对象存储空间中的一段范围的记录。游标有一个源(source)表明了哪个索引或者对象存储空间是跟这些在游标中迭代的记录相关联的。游标维护了序列中的位置, 其方向是按照记录的键单调递增或者单调递减的顺序。游标也有一对,代表了最近迭代的记录。游标最后有一个got value标志位。当这个标志位为false时,游标可能在加载下一个值得过程中或者到达了范围的末尾。当是true时,表明游标目前已经有值并且已经准备就绪,可以迭代下一条记录。

游标的方向有四种可能的取值。游标的方向取决了游标的初始位置是位于的开始处还是结尾处。它也决定了遍历时,游标移动的方向,或者遇到重复值是是否跳过。游标的方向允许的取值如下:

enum IDBCursorDirection {
    "next",
    "nextunique",
    "prev",
    "prevunique"
};
枚举描述
next 这个顺序使得游标在的开始处打开。当迭代的时候,游标应当发生在所有的记录上,包括那些重复的,按照键单调递增的顺序。
nextunique 这个顺序使得游标的开始处打开。当迭代的时候,游标不应该发生在那些有相同键的记录上,除了上述情况,所有的记录都要发生,按照键单调递增的顺序。对于重复值得每个键,只有第一条记录会发生。当对象存储空间或者唯一 索引时,方向有着跟"next"相同的行为。
prev 这个顺序使得游标在的结尾处打开。当迭代的时候,游标应当发生在所有的记录上,包括那些重复的,按照键单调递减的顺序。
prevunique 这个顺序使得游标结尾处打开。当迭代的时候,游标不应该发生在那些有相同键的记录上,除了上述情况,所有的记录都要发生,按照键单调递减的顺序。对于重复值得每个键,只有第一条记录会发生。当对象存储空间或者唯一 索引时,方向有着跟"prev"相同的行为。

如果游标的是一个对象存储空间,那么游标的有效对象存储空间 就是那个对象存储空间,游标的有效键是游标的位置。如果游标的是一个索引,游标的有效对象存储空间就是那个索引所引用的对象存储空间并且有效键是游标的对象存储空间位置。

在整个游标范围被迭代之前更改游标所遍历的记录集是允许的。为了保证能处理这个,游标不是用索引维护了他们的位置,而是用前一个返回记录的。对于一个向前迭代的游标,下一次游标会被要求迭代下一条记录并返回大于之前返回的的最小者。对于向后迭代的游标,情况则是相反,它会返回小于之前返回的的最大者。

对于游标迭代索引,则情况就有点复杂因为多条记录会有相同的键,因此也会按照排序。当迭代索引时,游标也有一个对象存储空间位置,,它代表了索引中之前发现的记录位置对象存储空间位置都用来查找下一个合适的记录。

游标对象实现了IDBCursor接口。只能有一个IDBCursor实例代表一个给定的游标。然而同一时刻可以打开的游标数量是不受限制的。

3.1.11 异常

每个indexedDB规范中定义的异常是一个有指定类型(type)字段的DOMException类型。[DOM4] 既有的DOM Level 4异常将会设置它们的code为一个遗留的值;然而,新的indexedDB类型异常的code值为0。message的值是可选的。

IndexedDB使用如下的新的DOMException类型和各种各样的信息字段。所有这些新的类型的code值为0

类型 消息 (可选)
UnknownError 操作失败,其原因是跟数据库本身不相关的并且没有被其他错误覆盖。
ConstraintError 一个事务中的变更操作失败,因为一个限制条件没有被满足。例如,一个对象比如对象存储空间或者索引已经存在并且一个请求试图创建一个新的。
DataError 为一项操作提供的数据不满足要求。
TransactionInactiveError 一个请求被放置于一个当前非活跃的事务当中,或者已经结束的事务。
ReadOnlyError 试图在一个"只读"事务中执行变更操作。
VersionError 试图打开一个比当前版本号低的数据库版本。

IndexedDB重用了下列的既有DOMException[DOM4]类型。这些类型将会继续按照DOM4规范返回code和name字段;然而,当indexedDB API抛出异常时,它们有如下的信息字段:

Type 信息 (可选)
NotFoundError 操作失败,因为请求的数据库对象找不到。例如,一个不存在的对象存储空间在被打开。
InvalidStateError 一项不被允许或者某个时刻不被允许的对象操作被执行。也发生在如果一个请求的源已经被删除或者移除。尽可能使用TransactionInactiveError或者ReadOnlyError,因为他们是InvalidStateError的具体变种。
InvalidAccessError 在对象上执行了一个非法操作。例如试图开启一个事务,但提供了一个为空的范围。
AbortError 请求被终止,例如通过IDBTransaction.abort
TimeoutError 事务的锁无法再可接受的时间内获得。
QuotaExceededError 操作失败,因为没有足够的存储空间,或者存储配额到达上限并且用户拒绝为数据库提供更多空间。
SyntaxError keypath参数含有非法的键路径
DataCloneError 被存储的数据不能被通过内部结构克隆算法克隆。
3.1.12 配置对象

Options objects are dictionary objects [WEBIDL] which are used to supply optional parameters to some indexedDB functions like createObjectStore andcreateIndex. The attributes on the object correspond to optional parameters on the function called.

The following WebIDL defines IDBObjectStoreParameters dictionary type.

dictionary IDBObjectStoreParameters {
    (DOMString or sequence<DOMString>)? keyPath = null;
    boolean                             autoIncrement = false;
};
autoIncrement of type  boolean, defaulting to  false
keyPath of type  (DOMString or sequence<DOMString>), nullable, defaulting to  null

The following WebIDL defines IDBIndexParameters dictionary type.

dictionary IDBIndexParameters {
    boolean unique = false;
    boolean multiEntry = false;
};
multiEntry of type  boolean, defaulting to  false
unique of type  boolean, defaulting to  false

The following WebIDL defines IDBVersionChangeEventInit dictionary type.

dictionary IDBVersionChangeEventInit : EventInit {
    unsigned long long  oldVersion = 0;
    unsigned long long? newVersion = null;
};
newVersion of type  unsigned long long, nullable, defaulting to  null
oldVersion of type  unsigned long long, defaulting to  0
3.1.13 Key Generators

When a object store is created it can be specified to use a key generator. A key generator keeps an internal current number. The current number is always a positive integer. Whenever the key generator is used to generate a new key, the generator's current number is returned and then incremented to prepare for the next time a new key is needed. Implementations must use the following rules for generating numbers when a key generator is used.

  • Every object store that uses key generators use a separate generator. I.e. interacting with one object store never affects the key generator of any other object store.
  • The current number of a key generator is always set to 1 when the object store for that key generator is first created.
  • When a key generator is used to generate a new key for a object store, the key generator's current number is used as the new key value and then the key generator's current number is increased by 1.
  • When a record is stored and an key value is specified in the call to store the record, if the specified key value is a Number greater than or equal to the key generator's current number, then the key generator's current number is set to the smallest integer number greater than the explicit key. A key can be specified both for object stores which use in-line keys, by setting the property on the stored value which the object store's key path points to, and for object stores which use out-of-line keys, by passing a key argument to the call to store the record.

    Only specified keys values which are Number values affect the current number of the key generator. Dates and Arrays which contain Numbers do not affect thecurrent number of the key generator. Nor do DOMString values which could be parsed as numbers. Negative Numbers do not affect the current number since they are always lower than the current number.

  • Modifying a key generator's current number is considered part of a database operation. This means that if the operation fails and the operation is reverted, the current number is reverted to the value it had before the operation started. This applies both to modifications that happen due to thecurrent number getting increased by 1 when the key generator is used, and to modifications that happen due to a record being stored with a key value specified in the call to store the record.
  • Likewise, if a transaction is aborted, the current number of the key generator's for all object stores in the transaction's scope is reverted to the values they had before the transaction was started.
  • When the current number of a key generator reaches above the value 2^53 (9007199254740992) any attempts to use the key generator to generate a new key will result in a ConstraintError. It is still possible to insert records into the object store by specifying an explicit key, however the only way to use a key generator again for the object store is to delete the object store and create a new one.
    NOTE

    As long as key generators are used in a normal fashion this will not be a problem. If you generate a new key 1000 times per second day and night, you won't run into this limit for over 285000 years.

  • The current number for a key generator never decreases, other than as a result of database operations being reverted. Deleting a record from an object store never affects the object store's key generator. Even clearing all records from an object store, for example using the clear() function, does not affect the current number of the object store's key generator.

A practical result of this is that the first key generated for an object store is always 1 (unless a higher numeric key is inserted first) and the key generated for an object store is always a positive integer higher than the highest numeric key in the store. The same key is never generated twice for the same object store unless a transaction is rolled back.

EXAMPLE 5

Each object store gets its own key generator:

store1 = db.createObjectStore("store1", { autoIncrement: true });
store1.put("a"); // Will get key 1
store2 = db.createObjectStore("store2", { autoIncrement: true });
store2.put("a"); // Will get key 1
store1.put("b"); // Will get key 2
store2.put("b"); // Will get key 2

If an insertion fails due to constraint violations or IO error, the key generator is not updated.

transaction.onerror = function(e) { e.preventDefault() };
store = db.createObjectStore("store1", { autoIncrement: true });
index = store.createIndex("index1", "ix", { unique: true });
store.put({ ix: "a"}); // Will get key 1
store.put({ ix: "a"}); // Will fail
store.put({ ix: "b"}); // Will get key 2

Removing items from an objectStore never affects the key generator. Including when .clear() is called.

store = db.createObjectStore("store1", { autoIncrement: true });
store.put("a"); // Will get key 1
store.delete(1);
store.put("b"); // Will get key 2
store.clear();
store.put("c"); // Will get key 3
store.delete(IDBKeyRange.lowerBound(0));
store.put("d"); // Will get key 4

Inserting an item with an explicit key affects the key generator if, and only if, the key is numeric and higher than the last generated key.

store = db.createObjectStore("store1", { autoIncrement: true });
store.put("a"); // Will get key 1
store.put("b", 3); // Will use key 3
store.put("c"); // Will get key 4
store.put("d", -10); // Will use key -10
store.put("e"); // Will get key 5
store.put("f", 6.00001); // Will use key 6.0001
store.put("g"); // Will get key 7
store.put("f", 8.9999); // Will use key 8.9999
store.put("g"); // Will get key 9
store.put("h", "foo"); // Will use key "foo"
store.put("i"); // Will get key 10
store.put("j", [1000]); // Will use key [1000]
store.put("k"); // Will get key 11
// All of these would behave the same if the objectStore used a
keyPath and the explicit key was passed inline in the object

Aborting a transaction rolls back any increases to the key generator which happened during the transaction. This is to make all rollbacks consistent since rollbacks that happen due to crash never has a chance to commit the increased key generator value.

db.createObjectStore("store", { autoIncrement: true });
trans1 = db.transaction(["store"], "readwrite");
store_t1 = trans1.objectStore("store");
store_t1.put("a"); // Will get key 1
store_t1.put("b"); // Will get key 2
trans1.abort();
trans2 = db.transaction(["store"], "readwrite");
store_t2 = trans2.objectStore("store");
store_t2.put("c"); // Will get key 1
store_t2.put("d"); // Will get key 2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值