今天看啥  ›  专栏  ›  sliiva

使用 indexedDB [译]

sliiva  · 掘金  ·  · 2020-03-31 11:10
阅读 58

使用 indexedDB [译]

原文链接:working with indexedDB

前言

本文将指导您完成 IndexedDB API 的基础知识 。我们使用的是 Jake Archibald 的 IndexedDB Promised 库,该库与IndexedDB API非常相似,但它使用的是 Promise 而不是事件。这在简化了 API 的同时又保持了其结构,因此您在使用此库学习到的任何内容都可以直接应用于IndexedDB API(以下代码都直接使用了 idb 库)。

IndexedDB API 很强大,但是对于简单的例子来说使用起来看起来会很麻烦,如果您更倾向于使用简洁的 API,可以尝试一些例如:localForagedaxie.jszangoDBidbidb-keyvalJSStorelovefiels这样的库,会使得 IndexedDB 更加程序员友好

什么是 IndexedDB?

IndexedDB是一个大型的,NoSQL 存储系统。它几乎可以让您在用户的浏览器中存储任何内容,除了常见的 search、get、put 操作之外,IndexedDB 还支持 transactions,在 MDN 上 IndexedDB 的定义如下:

“ IndexedDB 是用于客户端存储大量结构化数据(包括文件/ blob)的基础 API”,该 API 可以使用索引高性能的查询数据。同时 DOMStorage 经常用于存储少量数据,但它对于存储大量结构化数据时会受到容量的限制。这时 IndexedDB 为您提供了另一种解决方案。”

IndexedDB 类似 Localstorage 也遵循同源策略,每一个 IndexedDB 数据库的源头都是唯一的(通常指的是站点域或者子域),这意味着它不能获取其他源的数据或被任何其他源的页面访问。如果有数据存储限制,这个限制量通常也是很大的,但是不同的浏览器的限制范围和处理数据溢出的方式是不同的,查看深入阅读部分获得更多的信息。

IndexedDB 的相关概念

Database - 这是 IndexedDB 里数据结构的最高层级。它包含存储对象,而存储对象里又包含了您想要保留的数据。您可以使用任意名称创建多个数据库,但通常,每个应用程序只有一个数据库。

Object store - 存储对象是用于存储数据的单个存储空间。您可以认为存储对象类似于传统关系数据库中的表。通常,一个存储对象对应一类您存储的数据(不是 JavaScript 中的数据类型),比如有一个保存了博客文章和用户个人资料的应用程序,您就可以想象在数据库中有两个存储对象。与传统数据库中的表不同,存储对象中数据的实际 JavaScript 数据类型不需要保持一致(例如,在 'people' 存储对象中包含三个人,它们的 age 属性可以是 53,'twenty-five' 或者是未定义。)

Index - 索引是一种存储对象,用于根据数据的特有属性来组织另一个存储对象(称为引用存储对象)中的数据。索引会被用来通过属性在存储对象中检索记录,比如说,如果您正在保存一个人的数据,稍候,您也许就可以通过他的名字、年龄或者最喜欢的动物来找到他了。

Operation - 与数据库的交互操作.

Transaction - 事务围绕一个或一组操作进行封装,以确保数据库完整性。如果事务中的某个操作失败,则不会再进行任何其他操作,并且数据库将返回到事务开始之前的状态。IndexedDB 中的所有读写操作必须是事务的一部分。这样就可以进行连续的读-修改-写操作,而不必担心同时有其他线程在操作数据库。

Cursor - 一种遍历数据库中多个记录的机制。

检查 IndexedDB 的支持情况

由于并非所有浏览器都支持 IndexedDB,因此在使用它之前,我们需要检查用户的浏览器是否支持它。最简单的方法是在 window 对象中检查:

if (!('indexedDB' in window)) {
  console.log('This browser doesn\'t support IndexedDB');
  return;
}
复制代码

把这个函数放到页面脚本的第一行之后,我们就可以开始使用 IndexedDB 了。

打开一个数据库

使用 IndexedDB,您可以使用不同的名字创建多个数据库。但通常,每个应用程序只有一个数据库。要打开数据库,我们使用:

idb.open(name, version, upgradeCallback)
复制代码

此方法返回一个返回数据库对象的 Promise。在使用 idb.open 的时候,您需要提供名称,版本号和一个可选的回调函数来初始化这个数据库。

结合上下文,有一个使用 idb.open 的例子:

(function() {
  'use strict';

  //check for support
  if (!('indexedDB' in window)) {
    console.log('This browser doesn\'t support IndexedDB');
    return;
  }

  var dbPromise = idb.open('test-db1', 1);

})();
复制代码

我们将对 IndexedDB 支持的检查放在匿名函数的顶部。如果浏览器不支持 IndexedDB,则退出这个函数。我们调用 idb.open 打开一个名为 “ test-db1” 的数据库。在这个例子中,我们省略了可选的回调函数,让这个函数看起来更好理解。

使用存储对象

创建存储对象

数据库通常包含一个或多个存储对象。您可以认为存储对象类似于传统关系数据库中的表并且里面包含了相同类型的对象(不是 JavaScript 中的数据类型),例如,对于一个需要保存用户个人资料和 notes 的页面,我们可以想像数据库里面会有一个 “people” 存储对象里面保存着一个 “person” 对象,和一个 “notes” 存储对象。一个结构良好的 IndexedDB 数据库应为您需要保存的每类数据都提供一个对象存储。

为了确保数据库的完整性,只能在 idb.open 的回调函数中创建和删除存储对象。回调函数接收一个 UpgradeDB 的实例,该实例是 IDB Promised 库中用于创建对象存储的特有对象。在 UpgradeDB 对象上调用 createObjectStore 方法以创建对象存储:

upgradeDb.createObjectStore('storeName', options);
复制代码

这个方法的一个参数是定义的存储对象的名字,另一个参数是这个存储对象的配置对象,在里面我们可以定义各种配置属性。

下面是 createObjectStore 方法的使用示例:

(function() {
  'use strict';

  //check for support
  if (!('indexedDB' in window)) {
    console.log('This browser doesn\'t support IndexedDB');
    return;
  }

  var dbPromise = idb.open('test-db2', 1, function(upgradeDb) {
    console.log('making a new object store');
    if (!upgradeDb.objectStoreNames.contains('firstOS')) {
      upgradeDb.createObjectStore('firstOS');
    }
  });

})();
复制代码

同样,我们首先检查浏览器是否支持 IndexedDB。这次为了创建存储对象我们声明了 idb.open 的回调函数。如果尝试创建数据库中已经存在的存储对象,浏览器将会抛出一个错误,因此我们将 createObjectStore 方法包装在 if 条件语句中来检查数据库中是否已经存在这个存储对象。在该 if 块内调用了 UpgradeDB 对象的 createObjectStore方法创建一个名为 'firstOS' 的存储对象

定义主键

在定义存储对象时,可以使用主键来确定在存储对象中如何唯一标识数据。您可以使用键路径或使用密钥生成器来定义主键。

'key path' 是一个始终存在并含有唯一值的属性。在上面的例子中,我们应该选择 email 地址为 'people' 存储对象的关键路径。

upgradeDb.createObjectStore('people', {keyPath: 'email'});
复制代码

本示例创建一个名为 'people' 的存储对象,并将 ' email' 属性作为主键。

您还可以使用密钥生成器,例如 autoIncrement。密钥生成器为添加到存储对象中的每个对象创建唯一值。默认情况下,如果我们不指定主键,IndexedDB 将创建一个主键并将其与数据分开存储。

upgradeDb.createObjectStore('notes', {autoIncrement:true});
复制代码

本示例创建一个名为 'notes' 的存储对象,并将 IndexedDB 自动分配的主键设置为自动递增的数字。

upgradeDb.createObjectStore('logs', {keyPath: 'id', autoIncrement:true});
复制代码

此示例与前面的示例相似,但是这次主键是 'id',并设置它的值为自动递增的数字。 Choosing which method to use to define the key depends on your data. If your data has a property that is always unique, you can make it the keypath to enforce this uniqueness. Otherwise, using an auto incrementing value makes sense. 选择哪个方法来定义主键取决于您的数据。如果您的数据具有始终唯一的属性,您可以将其作为保持唯一性的 keyPath 。否则,使用自动递增值是更好的选择。

让我们看一个例子:

function() {
  'use strict';

  //check for support
  if (!('indexedDB' in window)) {
    console.log('This browser doesn\'t support IndexedDB');
    return;
  }

  var dbPromise = idb.open('test-db3', 1, function(upgradeDb) {
    if (!upgradeDb.objectStoreNames.contains('people')) {
      upgradeDb.createObjectStore('people', {keyPath: 'email'});
    }
    if (!upgradeDb.objectStoreNames.contains('notes')) {
      upgradeDb.createObjectStore('notes', {autoIncrement: true});
    }
    if (!upgradeDb.objectStoreNames.contains('logs')) {
      upgradeDb.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
    }
  });
})();
复制代码

这段代码创建三个存储对象,以及在存储对象中定义主键的各种方式。

定义索引

索引是一种存储对象,用于通过指定属性从引用存储对象中检索数据。索引位于引用存储对象中,并包含与之相同的数据,但索引使用了指定的属性作为其唯一路径,而不是引用存储对象的主键。索引必须在您创建存储对象的时候创建并且它可以用来规定数据的唯一约束。

可以在存储对象的实例上调用 createIndex 方法来创建一个索引:

objectStore.createIndex('indexName', 'property', options);
复制代码

这个方法创建并返回了一个索引对象,createIndex 方法将传入的第一个参数作为索引对象的名称,第二个参数是您想要在数据中索引的属性,最后一个参数是对索引的配置:uniquemultiEntry,它决定了索引会如何工作。当 unique 设置为 true 时,一个键只能有一个值,multiEntry 决定了当 createIndex 函数第二个参数是一个数组的时候的行为,当它设置为 true 的时候,createIndex 会为每一个数组元素添加一个入口,否则的话就添加一个包含数组的单一入口。

这是一个例子:

(function() {
  'use strict';

  //check for support
  if (!('indexedDB' in window)) {
    console.log('This browser doesn\'t support IndexedDB');
    return;
  }

  var dbPromise = idb.open('test-db4', 1, function(upgradeDb) {
    if (!upgradeDb.objectStoreNames.contains('people')) {
      var peopleOS = upgradeDb.createObjectStore('people', {keyPath: 'email'});
      peopleOS.createIndex('gender', 'gender', {unique: false});
      peopleOS.createIndex('ssn', 'ssn', {unique: true});
    }
    if (!upgradeDb.objectStoreNames.contains('notes')) {
      var notesOS = upgradeDb.createObjectStore('notes', {autoIncrement: true});
      notesOS.createIndex('title', 'title', {unique: false});
    }
    if (!upgradeDb.objectStoreNames.contains('logs')) {
      var logsOS = upgradeDb.createObjectStore('logs', {keyPath: 'id',
        autoIncrement: true});
    }
  });
})();
复制代码

在这个例子中,peoplenotes 存储对象有索引,为了创建索引,我们首先将 createObjectStore 的返回值分配给一个变量以便我们能够调用它上面 createIndex 方法。

注意:每次在存储对象中写入数据的时候索引都会进行跟新,声明越多的索引意味着 IndexDB 需要做更多的工作。

使用数据

在这一部分,我们将会描述怎么创建、读取、更新和删除数据,这些操作都是异步操作,所以可以在 IndexedDB API 发起请求的时候使用 promises。这和直接使用 API 一样。我们可以在 idb.open 返回的数据库对象上直接调用 .then方法,而不是通过监听请求触发事件后开始与数据库进行交互。

IndexedDB 中的所有数据操作都在事务内部执行。每个操作都具有以下形式:

  1. 获取数据库对象
  2. 打开数据库事务
  3. 在事务中打开存储对象
  4. 在存储对象上执行操作 可以将事务视为一个操作或一组操作的安全包装。如果事务中的某个操作失败,则所有操作都会回滚。事务特定于一个或多个存储对象,这些存储对象是在我们打开事务时定义的,它们可以是只读的也可以是可以读写的。这表示了事务内部的操作是读取数据还是对数据库进行更改。

创建数据

若要创建数据,请在对象存储上调用 add 方法,然后传入要添加的数据。Add 有一个可选的第二个参数,可用于在创建时为单个对象定义主键,但它只能在 createObjectStore 函数没有指定键路径的时候使用,这里有一个例子:

someObjectStore.add(data, optionalKey);
复制代码

参数 data 可以是任何类型的数据:字符串,数字,对象,数组等。唯一的限制是,如果存储对象具有定义的键路径,则数据必须包含此属性,并且值必须是唯一的。add 方法会返回一个 Promise,当对象添加到存储对象之后就会被 resolve。

add 操作是包含在一个事务里的,因此即使 promise resolve 之后,也不一定意味着该添加成功了。请记住,如果事务中有一个操作失败,则事务中的所有操作都会回滚。为了确定添加操作是否成功,我们需要使用 transaction.complete 方法检查整个事务是否已完成。transaction.complete 是一个 promise,它将在整个事务执行完成时 resolve,会在事务执行出错时 reject。需要注意的是这个方法并没有关闭这个事务。事务会自己执行到结束。我们必须对所有“写入”操作都执行此检查,因为这是我们唯一确定数据库在写入之后已经发生改变的方法。

让我们看一个 add 方法的例子:

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readwrite');
  var store = tx.objectStore('store');
  var item = {
    name: 'sandwich',
    price: 4.99,
    description: 'A very tasty sandwich',
    created: new Date().getTime()
  };
  store.add(item);
  return tx.complete;
}).then(function() {
  console.log('added item to the store os!');
});
复制代码

首先,我们获取数据库对象。执行 dbPromise.thendbPromise 会返回一个数据库对象,然后将此对象传递给 .then 里面的回调函数。因为 dbPromise(idb.open)是一个 promise,所以我们可以确定在 .then 执行时,数据库会被调用,并且所有存储对象和索引已经准备好被使用。

下一步是通过在数据库上调用 transaction 方法打开一个事务 。此方法接收一个存储对象列表,该列表定义了事务的范围(如果它是单个存储对象,我们可以直接传递对象名称,而不传递名称数组,就像我们在示例中所做的那样,我们只想要 'store' 存储对象)。transaction 方法还有一个可选的第二个参数,确定了事务打开为 readonly 模式还是 readwrite 模式。默认情况下为只读。

然后,我们可以在此事务上打开 'store' 存储对象,并将其分配给 store 变量。现在,当我们调用 store.add 时,添加操作将在事务内进行。最后,返回 tx.complete,当事务执行完毕之后将打印一条执行成功的消息。

读取数据

通过存储对象上调用 get 方法来读取数据。传入 get 方法的参数为希望从存储对象里检索的对象的主键。这是一个基本示例:

someObjectStore.get(primaryKey);
复制代码

add 一样,该 get 方法会返回一个 Promise,并且必须在事务内发生。

让我们看一个 get 方法的示例:

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readonly');
  var store = tx.objectStore('store');
  return store.get('sandwich');
}).then(function(val) {
  console.dir(val);
});
复制代码

再次,我们通过获取数据库对象并创建一个事务来开始操作数据库。请注意,这一次这个事务是只读的,因为我们没有在事务内部修改数据库(即 putadddelete)。然后,我们在事务上获取存储对象并将结果分配给 store 变量。最后,我们返回 store.get 操作的结果并吧这个对象打印到控制台。

注意:如果尝试获取不存在的对象,则代表成功的回调函数仍会执行,只是结果是 undefined

更新数据

通过在存储对象上调用 put 方法来更新数据库。该 put方法与 add 方法非常相似,并且可以代替 add 方法在存储对象中添加数据。同样,传入 put 方法的参数为待修改的数据和可选的主键:

someObjectStore.put(data, optionalKey);
复制代码

同样,此方法在事务内部发生并返回一个 Promise。与 add 操作一样,为了确保更新操作确实被执行了,我们任要仔细检查 transaction.complete

这是使用 put 方法的例子:

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readwrite');
  var store = tx.objectStore('store');
  var item = {
    name: 'sandwich',
    price: 99.99,
    description: 'A very tasty, but quite expensive, sandwich',
    created: new Date().getTime()
  };
  store.put(item);
  return tx.complete;
}).then(function() {
  console.log('item updated!');
});
复制代码

要更新对象存储中的现有项,需要对包含和数据库中的对象相同的主键值的对象使用 put 方法。我们假设存储对象中 'store' 的关键路径是 'name' 属性,并且我们正在更新 'sandwich' 对象的价格和描述。这个数据库交互具有与创建和读取操作相同的结构:获取数据库对象、创建事务、在事务上打开存储对象、在存储对象上执行操作。

删除资料

若要删除数据,请调用存储对象上的 delete 方法。

someObjectStore.delete(primaryKey);
复制代码

这是一个简单的例子:

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readwrite');
  var store = tx.objectStore('store');
  store.delete(key);
  return tx.complete;
}).then(function() {
  console.log('Item deleted');
});
复制代码

数据库交互的结构与其他操作相同。请注意,我们再次通过返回 tx.complete 来检查整个事务是否已完成,以确保执行了删除操作。

获取所有数据

到目前为止,我们一次只从数据库中检索到一个对象。我们还可以使用 getAll 方法或使用游标从存储对象或索引中检索到所有数据(或子集)。

使用getAll方法

检索所有数据的最简单方法是调用存储对象或索引上的 getAll 的方法,如下所示:

someObjectStore.getAll(optionalConstraint);
复制代码

此方法返回存储对象中与指定的键或键范围(请参阅使用范围和索引)匹配的所有对象,如果没有给定参数,则返回存储对象中的所有对象。与所有其他数据库操作一样,此操作发生在事务内部。下面是一个简短的例子:

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readonly');
  var store = tx.objectStore('store');
  return store.getAll();
}).then(function(items) {
  console.log('Items by name:', items);
});
复制代码

这里,我们在 ‘store’ 存储对象上调用 getAll。这将返回按主键排序的存储对象中的所有对象。

使用游标

检索所有数据的另一种方法是使用游标。游标逐个选择对象存储或索引中的每个对象,让您在选择数据时对其进行处理。与其他数据库操作一样,游标在事务中工作。

我们通过调用存储对象上的 openCursor 方法来创建游标,如下所示:

someObjectStore.openCursor(optionalKeyRange, optionalDirection);
复制代码

此方法返回一个 promise,该 promise resolve 返回代表存储对象中第一个对象的游标对象,如果没有对象则返回 undefined。然后调用 cursor.continue 将游标移动到下一个对象。同样它返回一个 resolve 下一个对象或者没有对象时 resolve(undefined)promise。我们将其放入一个循环中,以便一个接一个地遍历存储区中的所有条目。openCursor 方法中的可选键范围将迭代限制为存储区中对象的子集。方向选项可以为 nextprev,指定数据前向或后向遍历。

下一个示例使用游标遍历 ‘store’ 存储对象中所有项,并将它们打印到控制台:

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readonly');
  var store = tx.objectStore('store');
  return store.openCursor();
}).then(function logItems(cursor) {
  if (!cursor) {
    return;
  }
  console.log('Cursored at:', cursor.key);
  for (var field in cursor.value) {
    console.log(cursor.value[field]);
  }
  return cursor.continue().then(logItems);
}).then(function() {
  console.log('Done cursoring');
});
复制代码

与之前操作一样,我们首先获取数据库对象,创建一个事务,然后打开一个存储对象。我们在存储对象上调用 openCursor 方法,并在将游标对象传递给 .then 里面的回调函数。这一次我们将回调函数命名为 ‘logItems’,这样我们就可以从函数内部进行循环调用这个函数。如果 store.openCursor() 或者 sor.continue() 函数返回的 promise resolve(undefined) (表示没有更多的对象),则在 if (!cursor) {return;} 这行中断循环。

游标对象包含一个表示该项主键的键属性。它还包含一个表示数据的值属性。在 logItems 结束时,我们返回指针 .continue().then(logItems)cursor.continue 方法返回 resolve 代表存储对象中下一项的游标对象或没有对象时 resolve(undefined) 的 promise。为了 让函数循环执行,返回值被传入 .then 中的回调函数 logItems。因此,logItems 会继续调用自己,直到没有对象存在。

使用范围和索引

我们可以通过几种不同的方式来获取所有数据,但是如果我们只希望基于特定属性的数据子集怎么办?这就是索引的用处。索引使我们可以通过除主键之外的属性来获取存储对象中的数据。我们可以使用任何属性创建索引(该属性将成为索引的关键路径),在该属性上指定范围,并使用 getAll 方法或游标在该范围内获取数据。

我们使用 IDBKeyRange 对象定义范围。此对象有四个用于定义范围限制的方法:upperBound,lowerBound,bound(包括前两者)和 only。正如预期,upperBound和lowerBound方法指定了一个范围的上限和下限。

IDBKeyRange.lowerBound(indexKey);
// or
IDBKeyRange.upperBound(indexKey);
复制代码

它们每个都接受一个参数,该参数是您要指定为上限或下限的项目的索引键路径值。

bound 方法用于同时指定上限和下限,并将下限作为第一个参数:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);
复制代码

默认情况下,这些函数的范围是闭区间的,但是可以通过将 true 作为第二个参数(或者在 bound 函数中,作为第三个参数和第四个参数,分别限制下限和上限)来指定为开区间。闭区间包括上限或者下限的数据。而开区间则不包括。

让我们看一个例子。对于这个 demo,我们已经在 ‘store’ 存储对象中的 ‘price’ 属性上创建了一个索引。我们还添加了一个小表单,其中包含两个输入,用于获取取值范围的上限和下限。假设我们将表示价格的浮点数传递给函数的上下界:

function searchItems(lower, upper) {
  if (lower === '' && upper === '') {return;}

  var range;
  if (lower !== '' && upper !== '') {
    range = IDBKeyRange.bound(lower, upper);
  } else if (lower === '') {
    range = IDBKeyRange.upperBound(upper);
  } else {
    range = IDBKeyRange.lowerBound(lower);
  }

  dbPromise.then(function(db) {
    var tx = db.transaction(['store'], 'readonly');
    var store = tx.objectStore('store');
    var index = store.index('price');
    return index.openCursor(range);
  }).then(function showRange(cursor) {
    if (!cursor) {return;}
    console.log('Cursored at:', cursor.key);
    for (var field in cursor.value) {
      console.log(cursor.value[field]);
    }
    return cursor.continue().then(showRange);
  }).then(function() {
    console.log('Done cursoring');
  });
}
复制代码

代码首先获取极限的值并检查极限值是否存在。下一段代码将根据这些值来决定使用哪个方法来限制范围。在数据库交互中,我们像往常一样打开事务上的存储对象,然后打开存储对象上的 ‘price’ 索引。‘price’ 索引允许我们按价格搜索商品。我们在索引上打开一个游标并传入限值范围。这时游标返回一个表示在限制范围内第一个对象的 promise,如果在限制范围内没有找到相应对象,则返回 undefined。cursor.continue 将返回一个表示下一个对象的游标对象,以此类推,直到到达传入的范围的末端。

使用数据库版本控制

调用 idb.open 时,我们可以在第二个参数中指定数据库版本号。如果该版本号大于现有数据库的版本,则执行升级回调函数,从而使我们可以向数据库添加存储对象和索引。

注意:如果尝试创建数据库中已经存在的存储对象或索引时,浏览器将报错。我们可以在 if 语句中封装对 createObjectStore 的调用,使用 upgradedb.objectstorename .contains('objectStoreName') 检查对象存储是否已经存在。类似在下一个示例中那样,我们还可以使用 switch 语句检查 oldVersion 属性。

UpgradeDB 对象具有独特的 oldVersion 属性,该属性的值为浏览器中现有数据库的版本号。我们可以将此版本号传递到一条 switch 语句中,以执行基于现有数据库版本号的升级回调函数中的代码块。让我们来看一个例子:

var dbPromise = idb.open('test-db7', 2, function(upgradeDb) {
  switch (upgradeDb.oldVersion) {
    case 0:
      upgradeDb.createObjectStore('store', {keyPath: 'name'});
    case 1:
      var peopleStore = upgradeDb.transaction.objectStore('store');
      peopleStore.createIndex('price', 'price');
  }
});
复制代码

在该示例中,我们将数据库的最新版本设置为 2。当该代码首次执行时,由于浏览器中尚不存在该数据库,因此它 upgradeDb.oldVersion 为 0,并且 switch 语句从 case 0 处开始。在这段代码中,会将 'store' 存储对象添加到数据库中。通常,在 switch 语句中,每一个 case 都有一个中断,但是我们故意不在此处这样做。这样,如果现有数据库落后几个版本(或者不存在),则代码将继续执行其余的 case 代码块,直到执行了所有的最新操作。因此,在这个例子中,浏览器继续执行 case 1,在 'store' 存储对象上创建 'price' 索引。一旦这个执行操作完成后,浏览器中的数据库的版本更新到 2,并包含带有 'price' 索引的 'store' 存储对象。

假设我们现在想要在 'store' 存储对象上创建一个 'description' 索引。我们需要更新版本号并添加一个 case,如下:

var dbPromise = idb.open('test-db7', 3, function(upgradeDb) {
  switch (upgradeDb.oldVersion) {
    case 0:
      upgradeDb.createObjectStore('store', {keyPath: 'name'});
    case 1:
      var storeOS = upgradeDb.transaction.objectStore('store');
      storeOS.createIndex('price', 'price');
    case 2:
      var storeOS = upgradeDb.transaction.objectStore('store');
      storeOS.createIndex('description', 'description');
  }
});
复制代码

假设我们在上一个示例中创建的数据库仍然存在于浏览器中,则该数据库在执行时 upgradeDb.oldVersion 为 2, 浏览器将跳过case 0case 1,直接执行 case 2 中的代码,里面创建了一个 'description' 索引。完成所有这些操作之后,浏览器中的数据库版本将更新为版本 3,并且数据库将包含一个带有 'price' 和 'description' 索引的 'store' 存储对象。

进一步阅读

IndexedDB 文档

使用 IndexedDB -MDN

indexedDB 背后的基本概念 -MDN

数据库索引 API -W3C

数据存储限制

在移动设备浏览器上使用限额

浏览器存储限制和逐出条件

附录

IndexedDB API和IndexedDB Promised库的比较

IndexedDB Promised 库基于 IndexedDB API 并将其请求转换成 promise。库和 API 之间的总体结构是相同的,并且通常来说,实际操作数据库的语法也是相同的,并且它们将以相同的方式起执行。但是由于 requestpromise 之间存在的差异而使它们之间有一些差异,我们将在此处介绍这些差异。

在 IndexedDB API 中所有数据库交互的请求都有相关的 onsuccessonerror 事件处理程序。这于 .then.catchpromise 函数类似。indexedDB.open 原始 API中有一个特殊的事件处理函数: onupgradeneeded,用于创建存储对象和索引。这与 Promise 库中 idb.open 的升级回调函数也类似。实际上,如果您看过 Promised 库源码,就会发现升级回调函数只是对 onupgradeneeded 事件处理程序进行了简单的包装。

让我们看一个使用 IndexedDB API 的示例。在此示例中,我们将打开一个数据库,添加一个存储对象,然后向该存储对象中添加一组数据:

var db;

var openRequest = indexedDB.open('test_db', 1);

openRequest.onupgradeneeded = function(e) {
  var db = e.target.result;
  console.log('running onupgradeneeded');
  if (!db.objectStoreNames.contains('store')) {
    var storeOS = db.createObjectStore('store',
      {keyPath: 'name'});
  }
};
openRequest.onsuccess = function(e) {
  console.log('running onsuccess');
  db = e.target.result;
  addItem();
};
openRequest.onerror = function(e) {
  console.log('onerror!');
  console.dir(e);
};

function addItem() {
  var transaction = db.transaction(['store'], 'readwrite');
  var store = transaction.objectStore('store');
  var item = {
    name: 'banana',
    price: '$2.99',
    description: 'It is a purple banana!',
    created: new Date().getTime()
  };

 var request = store.add(item);

 request.onerror = function(e) {
    console.log('Error', e.target.error.name);
  };
  request.onsuccess = function(e) {
    console.log('Woot! Did it');
  };
}
复制代码

这段代码与本教程中的先前示例做的事情非常相似,除了不使用Promised库。我们可以看到与数据库交互的结构没有改变。在数据库对象的 upgrade 事件处理程序中上创建了存储对象,然后将需要保存的数据添加到存储对象中,事务的执行顺序与我们之前所看到例子的相同。不同之处在于,这是通过 request 和事件处理函数完成的,而不是通过 promisepromise 链完成的。

以下简单的比较了下 IndexedDB API 和 IndexedDB Promised 库之间的区别。

IndexedDB Promised IndexedDB API
Open database idb.open(name, version, upgradeCallback) indexedDB.open(name, version)
Upgrade database Inside upgradeCallback request.onupgradeneeded
Success .then request.onsuccess
Error .catch request.onerror



原文地址:访问原文地址
快照地址: 访问文章快照