IndexedDB 2.0 的新特性是什么?

Indexed Database API 2.0 的草案即将完成,它提供了一些用于精细控制 IndexedDB 的新 API。

好消息是,所有这些新 API 都在 Firefox 中实现,并将作为 Firefox 51 的一部分发布(目前已在 开发者版 中提供,计划于 2017 年 1 月正式发布)。在本文中,我们将分享一些关于如何使用这些 API 以更好的方式访问 IndexedDB 的示例。

IDBObjectStore.nameIDBIndex.name 的设置器

Indexed Database API 的前一个版本 中,IndexedDB 允许开发者通过添加/删除对象存储和索引来升级 IndexedDB 的模式;但无法重命名现有对象存储和索引。

这使得在原始名称不再准确反映要存储的对象的新模式时,无法正确命名索引和对象存储。此外,将对象移动到新的对象存储中只是为了重命名,这是浪费时间。

例如,假设有一个名为“文本消息”的对象存储,用于存储在移动网络上发送或接收的纯文本消息。之后,假设开发者想要提供更丰富的内容消息,包括图像、音频和视频等附件,那么最好将该对象存储命名为更通用的名称,例如“移动消息”。此外,想象一下,在这个对象存储中有一个名为“收件人”的索引,它表示消息的收件人地址,现在,开发者想要支持多收件人消息传递。那么,最好将该索引重命名为“收件人**s**”(复数)。

let request = indexedDB.open("messageDB", 2);
request.<strong>onupgradeneeded</strong> = (event) => {
  let txn = event.target.transaction;
  let store = txn.objectStore("text messages");

  store.<strong>name</strong> = "mobile messages";
  let index = store.index("recipient");
  index.<strong>name</strong> = "recipient<strong>s</strong>";
};

IDBDatabase.onclose()

IDBDatabase 接口中定义了一个新的 onclose 事件处理程序,允许开发者在脚本之外监听 IndexedDB 的存储更改。以 Firefox 浏览器为例,浏览器允许用户 清除单个网站使用的存储。使用此处理程序,脚本可以收到通知,告知数据库是在脚本之外强制关闭的,以便提供更及时的信息或向用户发出警告。

let request = indexedDB.open("bookstore");
request.onsuccess = (event) => {
  let db = event.target.result;

  db.<strong>onclose</strong> = (event) => {
    alert("the database: " + db.name + "was closed outside the script!");
  };
};

二进制键索引

在 2.0 实现中,二进制数据类型可以作为索引键进行处理。更准确地说,存储对象的任何由 Arraybuffer类型化数组对象DataView 表示的属性现在都可以被索引。这有利于开发者:现在您可以直接将二进制签名作为键,而无需像以前版本的 API 那样将它们序列化为字符串或数组对象。

IDBTransaction.objectStoreNames[]

这个方便的新属性为开发者提供了一种更简便的方法来确定当前事务中涉及的对象存储。

let request = indexedDB.open("bookstore", 2);
request.<strong>onupgradeneeded</strong> = (event) => {
  let txn = event.target.transaction; // txn.mode == "versionchange"
  txn.<strong>objectStoreNames</strong>; // a list of objectstore names can be accessed in this txn.
};

IDBObjectStore.getKey(query)

出于性能原因以及与 IDBIndex 的对称性,getKey(query) 现在已在 IDBObjectStore 中提供。使用这个新的 API,您无需采用启发式方法来检查特定范围内是否存在对象键。例如,openCursor(query) 可能是一种替代方法,但您仍然需要支付游标创建和对象序列化/反序列化的开销。

假设有一个对象存储用于记录网络活动,并且时间戳被选择作为对象键。现在,我们想知道最后 24 小时内第一次活动发生的时间。

let openRequest = indexedDB.open("telemetry");
openRequest.onsuccess = (event) => {
  let db = event.target.result;
  let store = db.transaction("netlogs").objectStore("netlogs");

  let today = new Date();
  let yesterday = new Date(today);
  yesterday.setDate(today.getDate() - 1);
  let request = store.<strong>getKey</strong>(IDBKeyRange(yesterday, today));
  request.onsuccess = (event) => {
    let when = event.target.result;
    alert("The 1st activity in last 24 hours was occurred at " + when);
  };
};

IDBObjectStore.openKeyCursor(range, direction)

openKeyCursor() 作为 IDBObjectStoreopenCursor() 的轻量级版本,允许您在指定范围内迭代对象键,而无需从数据库中检索整个对象。这减少了序列化开销。

让我们继续之前的示例,检索最后 24 小时内发生的网络活动的时间戳。

let openRequest = indexedDB.open("telemetry");
openRequest.onsuccess = (event) => {
  let db = event.target.result;
  let store = db.transaction("netlogs").objectStore("netlogs");

  let today = new Date();
  let yesterday = new Date(today);
  yesterday.setDate(today.getDate() - 1);
  let request = store.<strong>openKeyCursor</strong>(IDBKeyRange(yesterday, today));
  let timestamps = [];
  request.onsuccess = (event) => {
    let cursor = event.target.result;
    if (!cursor) { return; }
    timestamps.push(cursor.<strong>key</strong>);
    cursor.continue();
  };
};

getAll/getAllKeys(range, count) 来自 IDBObjectStoreIDBIndex

与通过迭代游标逐个检索数据不同,getAll()getAllKeys() 允许我们从 IDBObjectStoreIDBIndex 中以升序的方式一次性检索所有数据。您可以指定可选的 rangecount 来限制响应中的结果数量。当要检索的数据的总大小不是很大时,这非常有用。

例如,如果您想一次性检索最后 24 小时内的前十项活动。

let openRequest = indexedDB.open("telemetry");
openRequest.onsuccess = (event) => {
  let db = event.target.result;
  let store = db.transaction("netlogs").objectStore("netlogs");

  let today = new Date();
  let yesterday = new Date(today);
  yesterday.setDate(today.getDate() - 1);
  let activities = null;
  let request = store.<strong>getAll</strong>(IDBKeyRange(yesterday, today), <strong>10</strong>);
  request.onsuccess = (event) => {
    activities = event.target.result;
  };
};

注意:如果您想要以降序的方式一次性检索数据,并且指定了count,那么这不太有用。

IDBCursor.continuePrimaryKey(key, primaryKey)

在 IndexedDB 的设计中,IDBIndex 树中的记录按索引键的升序排列,然后按对象键的升序排列。借助 IDBIndex 中打开的 IDBCursorcontinuePrimaryKey(),开发者可以立即恢复在先前迭代中关闭的游标的迭代。您只需指定之前记录的索引键和主键,而不是从头开始迭代并逐个比较主键。

例如,以下是如何恢复对所有标记为“javascript”的文章的迭代,从您上次访问开始。

let request = articleStore.index("tag").openCursor();
let count = 0;
let unreadList = [];
request.onsuccess = (event) => {
    let cursor = event.target.result;
    if (!cursor) { return; }
    let lastPrimaryKey = <code>getLastIteratedArticleId();
    if (lastPrimaryKey > cursor.primaryKey) {
      cursor.continuePrimaryKey("javascript", lastPrimaryKey);
      return;
    }
    // update lastIteratedArticleId
    setLastIteratedArticleId(cursor.primaryKey);
    // preload 5 articles into the unread list;
    unreadList.push(cursor.value);
    if (++count < 5) {
      cursor.continue();
    }
};

IDBKeyRange.includes(key)

这是一个新的辅助函数,用于检查键是否在 IDBKeyRange 的范围内。

let openRange = IDBKeyRange.bound(5, 10, true, true);
openRange.<strong>includes</strong>(5);  // false;
openRange.<strong>includes</strong>(10); // false;
openRange.<strong>includes</strong>(7);  // true;

通过 2.0 的更改和更新,您可以使用 IndexedDB 实现全新的功能。看看这些新功能能带您去哪里,并告诉我们结果如何。

关于 Bevis Tseng

Bevis Tseng 是 Mozilla 的平台工程师,他曾负责 Firefox OS (B2G) 中与电话相关的功能。他最近完成了 Gecko 中 IndexedDB 的增强,使其符合 Spec v2,现在正参与 Quantum-DOM 调度程序的任务标记工作。

更多来自 Bevis Tseng 的文章……