使用游标
使用get()方法需要您知道您想要查询的数据的【键】。如果您想单步调试对象store的所有记录,您可以使用游标。如下所示:
var objectStore = db.transaction("customers").objectStore("customers");
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
cursor.continue();
}
else {
alert("No more entries!");
}
};
openCursor()方法带有几个参数。首先,您可以使用【键】的范围对象来限制查询项目的范围,该对象稍后介绍。第二,您可以指定迭代方向。上例中,我们按照升序迭代遍历所有对象。游标的success处理有点特殊,游标对象本身是请求request的结果result(我们使用了速记,它是event.target.result)。那么实际的【键】key和【值】value可以在游标对象的属性key和value中找到。如果要继续运行,您必须为游标呼叫continue()方法。到达数据尾部时(或者打开的游标中没有数据),您仍然会得到success回调处理,但是该结果的属性是未定义的。
通常的做法是,查询一个对象store的所有数据,然后存放在数组中,如下所示:
var customers = [];
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
customers.push(cursor.value);
cursor.continue();
}
else {
alert("Got all customers: " + customers);
}
};
注意:Mozilla也实现了getAll()方法来处理这种情况(还有getAllKeys(),现在已经隐藏到dom.indexedDB.experimental).这些不是IndexedDB标准的一部分,所以它们将来会消失。介绍这些是因为有用。下面代码精确地实现了同样的功能:
objectStore.getAll().onsuccess = function(event) {
alert("Got all customers: " + event.target.result);
};
使用游标的属性值会带来一定的性能消耗,因为对象是被惰性创建的。当您使用getAll()方法时,Gecko必须一次性创建所有的对象。比如,如果您只是每个【键】感兴趣, 使用游标会比呼叫getAll()方法性能高很多。但是,如果您想要一个包含指定对象store的所有记录的数组,请使用getAll()方法。
使用索引
以ssn为主键,存储顾客数据是合理的,因为每个ssn的值具有唯一性。(这是否是一个好主意,暂且不讨论)。
如果您需要以来查询顾客信息的名字name,您需要迭代遍历数据库中的每个ssn,直到您找到。这样的方式查询速度会很慢,因此,您应该使用索引index。
var index = objectStore.index("name");
index.get("Donna").onsuccess = function(event) {
alert("Donna's SSN is " + event.target.result.ssn);
};
名字属性name不是唯一的,因此可能有多个记录的name值是“Donna”。此时,您将得到【键】最小的记录对应的name值。如果您需要访问指定name值的所有记录,您可以使用游标。您可以在index上打开两个不同类型的游标。一个常规的游标会映射(maps)index的属性值到对象store中的对象。一个【键】游标会映射index的属性值到对象store中对象的【键】。不同之处举例说明如下:
// Using a normal cursor to grab whole customer record objects
index.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key is a name, like "Bill", and cursor.value is the whole object.
alert("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);
cursor.continue();
}
};
// Using a key cursor to grab customer record object keys
index.openKeyCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key is a name, like "Bill", and cursor.value is the SSN.
// No way to directly get the rest of the stored object.
//alert("Name: " + cursor.key + ", SSN: " + cursor.value);官方文档有误,在chrome和ff实测,应该是如下语句
alert("Name: " + cursor.key + ", SSN: " + cursor.primaryKey);
cursor.continue();
}
};
指定范围和游标的方向
如果您要指定游标的范围,可以使用IDBKeyRange对象并把它传递给openCursor() or openKeyCursor()的第一个参数。您可以把【键】的范围设置成只有一个单独的【键】,或者带有上限或者下限,或者上下限都有。上下界限可以封闭的(例如,【键】的范围包含边界值)或者开放的(例如,【键】的范围不包含边界值。工作方式如下所示:
// Only match "Donna"
var singleKeyRange = IDBKeyRange.only("Donna");
// Match anything past "Bill", including "Bill"
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");
// Match anything past "Bill", but don't include "Bill"
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);
// Match anything up to, but not including, "Donna"
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);
// Match anything between "Bill" and "Donna", but not including "Donna"
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
// To use one of the key ranges, pass it in as the first argument of openCursor()/openKeyCursor()
index.openCursor(boundKeyRange).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the matches.
cursor.continue();
}
};
有时候,您可能想按照降序而不是升序遍历数据集(默认的方向是升序)。切换遍历方向的功能已经被华丽丽的实现了,只需要把“prev”作为第二个参数传递给openCursor()。
objectStore.openCursor(boundKeyRange, "prev").onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the entries.
cursor.continue();
}
};
如果您只是想指定遍历方向而不需要过滤结果,您可以传递一个null给openCursor()作为第一个参数
objectStore.openCursor(null, "prev").onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the entries.
cursor.continue();
}
};
“name”索引值不具有唯一性,可能存在多个相同的值。注意,这种情况不能发生在对象store,因为【key】必须具有唯一性。如果您想在游标中过滤掉索引的多个重复值,您可以传递“nextunique”(或者“prevunique”,如果您要向前查询)作为方向参数。一旦用了“nextunique”或者“prevunique”,带有最小【键】的记录将被返回。
index.openKeyCursor(null, "nextunique").onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the entries.
cursor.continue();
}
};
了解方向参数的信息,请参考IDBCursor Constants
数据库版本变化然而web应用在另一个标签页中被打开。
当您的web应用有变更时,此种情况下,数据库也需要变更。此时,如果用户在一个标签页打开了旧的程序,
而在另一个标签页打开了新的程序,您应该考虑会发生什么问题。当您以高于现有数据库(版本号)的版本号为参数呼叫open()时,在更改数据库之前,所有其它已经打开的数据库必须显式通知该请求(阻塞事件将被触发,直到他们被关闭或者重新载入)。举例如下:
var openReq = mozIndexedDB.open("MyTestDatabase", 2);
openReq.onblocked = function(event) {
// If some other tab is loaded with the database, then it needs to be closed
// before we can proceed.
alert("Please close all other tabs with this site open!");
};
openReq.onupgradeneeded = function(event) {
// All other databases have been closed. Set everything up.
db.createObjectStore(/* ... */);
useDatabase(db);
};
openReq.onsuccess = function(event) {
var db = event.target.result;
useDatabase(db);
return;
};
function useDatabase(db) {
// Make sure to add a handler to be notified if another page requests a version
// change. We must close the database. This allows the other page to upgrade the database.
// If you don't do this then the upgrade won't happen until the user closes the tab.
db.onversionchange = function(event) {
db.close();
alert("A new version of this page is ready. Please reload!");
};
// Do stuff with the database.
}
您也需要监听版本错误消息,以处理这样的情况:已经打开的应用,试图使用过期的版本号来打开数据库。
安全
IndexedDB使用同源策略,意味着把store绑定在创建它的网站上(通常,是站点的域或者子域),因此它不能被其它的站点访问。第三方的窗口可以访问宿主站点的IndexedDB store,除非浏览器设置为不接受第三方的cookies。
警惕浏览器关闭
如果浏览器关闭(因为用户点击了退出操作),硬盘包含的数据库将被意外删除,数据库无法访问,如下的事情会发生:
1涉及该数据库的每个事务会因为AbortError事件中止。作用与呼叫IDBTransaction.abort()相同。
2一旦所有的事务结束,数据库连接将关闭。
3最终,IDBDatabase对象代表的数据库连接会收到关闭事件。您可以使用IDBDatabase.onclose事件处理来监听该事件,这样您就能知道数据库什么时候意外关闭了。
以上描述的行为是新增的,只在下面的浏览器中有效:
Firefox 50, Google Chrome 31 (类似)
这些的版本之前的浏览器,事务会被悄悄的中止,不会触发关闭事件,因此也无法探测数据库何时被异常关闭。
因为用户可以在任何时候退出浏览器,这意味着您不能指望每个事务都顺利完成,而且在旧版本的浏览器中,您
甚至不知道它们是什么时候结束的。这意味着:
首先,在每个事务结束时,您应该小心地让数据库处于一致的状态。比如,假定您使用IndexedDB存储一个允许用户编辑的项目列表。用户编辑之后,您清除数据库中的原来的数据,把编辑之后的内容重新写入数据库。如果您在一个事务中清除数据库原有内容,而在另一个事务中向数据库写入新的内容。这很危险,浏览器将在清除数据库之后,新内容写入数据库之前被关闭,留给您一个空数据库。为避免此种情况,您应该在同一个事务中实施清除数据库内容和写入数据库内容。
其次,您永远不要把数据库事务绑定在unload事件。如果unload事件被关闭浏览器的行为触发,在unload事件处理中创建的任何事务将不会结束。为了在浏览器会话中保持一些信息,直观的方法是:打开浏览器时,读取数据库内容,用户操作时,记录数据的变更,关闭浏览器时,把新数据保存到数据库中。然而,此法行不通。数据库事务将在unload事件处理中被创建,但是因为它们是异步的,它们在执行之前将被中止。
实际上,没有方法来保证IndexedDB事务顺利完成,甚至在正常浏览器关闭的情况下。浏览器正常关闭时,您可以跟踪事务,添加beforeunload事件,这样,在unloading时,如果您的事务尚未完成,可以向用户提示警告信息。至少通过abort通知和IDBDatabase.onclose处理程序,您能知道(unload)是何时发生的。
本地化智能排序
Mozilla在Firefox 43+版本中已经实现了IndexedDB数据的本地化排序。默认地,IndexedDB根本不处理国际化字符串排序,所有的东西都作为英文排序。比如,b,á, z, a 排序如下:
a
b
z
á
显然不符合用户的预期——比如Aaron and Áaron应该是处于同一个通讯录的前后相邻的位置。实现适当的国际化排序需要把整个数据库读入内存,然后用客户端的javascript语言排序,效率并不够。
这个新的功能允许开发者在使用IDBObjectStore.createIndex()创建index时,指定本地语言集。这种情况下,使用游标遍历数据集时,而且您希望指定本地化智能排序,您可以使用对应的IDBLocaleAwareKeyRange。
IDBIndex也包含了一些新属性用来判断是否有本地化设置,它们是:locale(返回locale,或者null——如果没有设置本地化)和isAutoLocale(返回true——如果索引使用智能本地化创建——意味着使用平台的默认本地设置;否则false)。
注意:该特征隐藏于一个flag之中,如需开启和尝试,请到about:config并开启dom.indexedDB.experimental.