本地数据库IndexedDB - 初学者

        IndexedDB 是一种可以让你在用户的浏览器内持久化存储数据的方法。IndexedDB 为生成 Web Application 提供了丰富的查询能力,使我们的应用在在线和离线时都可以正常工作。

一、基本模式

        IndexedDB建议使用的基本模式如下所示:

  1. 打开数据库。
  2. 在数据库中创建一个对象库(Object store)。
  3. 启动一个事务,并发送一个请求来执行一些数据库操作,像增加或提取数据等。
  4. 通过监听正确类型的DOM事件等待操作完成。
  5. 在 操作结果上进行下一步操作(可以在request对象中打到)

二、生成和构建一个对象存储空间

        由于IndexedDB本身的规范还在持续更新中,当前的IndexedDB的实现还是使用浏览器前缀。在规范更加稳定之前,浏览器厂商对于标准IndexedDB可能都会有不同的实现。但是一旦大家对规范达到共识,厂商就会不带前缀标记的进行实现。实际上一些实现已移除了浏览器前缀:IE10、Firefox 16和Chrome24。当使用前缀的时候,基于Gecko内核浏览器使用moz前缀,基于Webkit内核浏览器会使用webkit前缀。

        如何还在使用旧浏览器,可以使用下例代码:

// In the following line, you should include the prefixes of implementations you want to test.
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
// DON'T use "var indexedDB = ..." if you're not in a function.
// Moreover, you may need references to some window.IDB* objects:
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange
// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)

//判断IndexedDB对象是否存在
if (!window.indexedDB) {
    window.alert("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.")
}

三、数据库

3.1 打开数据库       

open请求不会立即打开数据库或者开始一个事务。对open()函数的调用会返回一个可以作为事件来处理的包含result(成功的话)或者错误值的IDBOpenDBRequest(es-US)对象。在IndexedDB中的大部分异步方法做都是同样的事件:返回一个包含result或者错误的IDBOpenDBRequest(es-US)对象。open函数的结果是一个IDBOpenDBRequest(es-US)对象实例。

代码如下:

var request = window.indexedDB.open("TestDatabase");

3.2 版本号

open方法接受第二个参数,是数据库版本号。数据库版本决定了数据库架构,即数据库的对象仓库(object store)和他的结构。如果数据库不存在,open操作会创建数据库,然后onupgradeneeded事件被触发,需要在onupgradeneeded事件中,允许你在处理函数中更新数据库模式。

代码如下:

// 错误写法
var request = window.indexedDB.open("TestDatabase", 2.4);

// 正确写法
var request = window.indexedDB.open("TestDatabase", 2);

注:版本号是一个unsigned long long 数字,意味着它可以是一个特别大的数字,但不能使用浮点数,否则它将会四舍五入,这可能导致 onupgradeneeded 事件不会被触发。

3.3 生成处理函数

        IndexedDB几乎所有产生的请求,处理的时候首先要做的就是添加成功和失败处理函数,代码如下:

// 请求出错 执行函数
request.onerror = function(event) {
  // Do something with request.errorCode!
};

// 请求成功 执行函数
request.onsuccess = function(event) {
  // Do something with request.result!
};

        一切顺利的话,success事件(即一个type属性被 设置成"success"的DOM事件)会被触发,request会作为它的target。一旦它被触发的话,相关request的onsuccess()处理函数就会被触发,使用success事件作为它的参数。否则如果不是所有事件都成功的话,一个error事件(即type属性被设置成"error"的DOM事件)会在request上被触发。这将会触发使用error事件作为参数的onerror()方法。

3.4 创建和更新数据库版本号

        当创建一个新的数据库或者增加已存在的数据库版本号(当打开数据库时,指定一个比之前更大的版本号),onupgradeneeded事件会被触发,IDBVersionChangeEvent (en-US) 对象会作为参数传递给绑定在 request.result(例如例子中的 db)上的 onversionchange 事件处理函数,你应该在此创建该版本需要的对象仓库(object store)。

        要更新数据库的 schema,也就是创建或者删除对象存储空间,需要实现 onupgradeneeded 处理程序,这个处理程序将会作为一个允许你处理对象存储空间的 versionchange 事务的一部分被调用。

代码如下:

// 该事件仅在较新的浏览器中实现了
request.onupgradeneeded = function(event) {
  // 保存 IDBDataBase 接口
  var db = event.target.result;

  // 为该数据库创建一个对象仓库
  var objectStore = db.createObjectStore("name", { keyPath: "uid" });
};

        在这种情况下,数据库会保留之前版本数据库的对象仓库(object store),因此你不必再次创建这些对象仓库。你需要创建新的对象仓库,或删除不再需要的上一版本中的对象仓库。如果你需要修改一个已存在的对象仓库(例如要修改 keyPath),你必须先删除原先的对象仓库然后使用新的设置创建。(注意,这样会丢失对象仓库里的数据,如果你需要保存这些信息,你要在数据库版本更新前读取出来并保存在别处)。

        尝试创建一个与已存在的对象仓库重名(或删除一个不存在的对象仓库)会抛出错误。

        如果 onupgradeneeded事件成功执行完成,打开数据库请求的 onsuccess 处理函数会被触发。

3.5 构建数据库

        IndexedDB使用对象存仓库而不是表,并且一个单独的数据库可以包含任意数量的对象存储空间。每当一个值被存储进一个对象存储空间时,它会和一个键相关联。键的提供可以几种不同的方法,这取决于对象存储空间是使用key path 还是key generator。

键路径 (keyPath) 键生成器 (autoIncrement) 描述
No No 这种对象存储空间可以持有任意类型的值,甚至是像数字和字符串这种基本数据类型的值。每当我们想要增加一个新值的时候,必须提供一个单独的键参数。
Yes No 这种对象存储空间只能持有 JavaScript 对象。这些对象必须具有一个和 key path 同名的属性。
No Yes 这种对象存储空间可以持有任意类型的值。键会为我们自动生成,或者如果你想要使用一个特定键的话你可以提供一个单独的键参数。
Yes Yes 这种对象存储空间只能持有 JavaScript 对象。通常一个键被生成的同时,生成的键的值被存储在对象中的一个和 key path 同名的属性中。然而,如果这样的一个属性已经存在的话,这个属性的值被用作键而不会生成一个新的键。

        你也可以使用对象存储空间持有的对象,不是基本数据类型,在任何对象存储空间上创建索引。索引可以让你使用被存储的对象的属性的值来查找存储在对象存储空间的值,而不是用对象的键来查找。

        此外,索引具有对存储的数据执行简单限制的能力。通过在创建索引时设置 unique 标记,索引可以确保不会有两个具有同样索引 key path 值的对象被储存。因此,举例来说,如果你有一个用于持有一组 people 的对象存储空间,并且你想要确保不会有两个拥有同样 email 地址的 people,你可以使用一个带有 unique 标识的索引来确保这些。

        这听起来可能有点混乱,但下面这个简单的例子应该可以解释这些概念。首先,我们定义一些将在例子中用到的客户数据。

// 我们的客户数据看起来像这样。
const customerData = [
  { ssn: "444-44-4444", name: "Bill", age: 35, email: "[email protected]" },
  { ssn: "555-55-5555", name: "Donna", age: 32, email: "[email protected]" }
];

        当然,你不会使用人们的社会保险号(ssn)作为客户表的主键,因为不是每个人都拥有社会保险号,并且你应该存储他们的生日而不是年龄。为了方便,这里我们忽略这些不合理的设计,继续往下看。

        现在让我们看看如何创建一个 IndexedDB 来存储上面的数据:

const dbName = "the_name";

var request = indexedDB.open(dbName, 2);

request.onerror = function(event) {
  // 错误处理
};
request.onupgradeneeded = function(event) {
  var db = event.target.result;

  // 建立一个对象仓库来存储我们客户的相关信息,我们选择 ssn 作为键路径(key path)
  // 因为 ssn 可以保证是不重复的
  var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

  // 建立一个索引来通过姓名来搜索客户。名字可能会重复,所以我们不能使用 unique 索引
  objectStore.createIndex("name", "name", { unique: false });

  // 使用邮箱建立索引,我们向确保客户的邮箱不会重复,所以我们使用 unique 索引
  objectStore.createIndex("email", "email", { unique: true });

  // 使用事务的 oncomplete 事件确保在插入数据前对象仓库已经创建完毕
  objectStore.transaction.oncomplete = function(event) {
    // 将数据保存到新创建的对象仓库
    var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
    customerData.forEach(function(customer) {
      customerObjectStore.add(customer);
    });
  };
};

        正如前面提到的,onupgradeneeded 是我们唯一可以修改数据库结构的地方。在这里面,我们可以创建和删除对象存储空间以及构建和删除索引。

        对象仓库仅调用 createObjectStore() 就可以创建。这个方法使用仓库的名称,和一个参数对象。即便这个参数对象是可选的,它还是非常重要的,因为它可以让你定义重要的可选属性,并完善你希望创建的对象存储空间的类型。在我们的示例中,我们创建了一个名为“customers”的对象仓库并且定义了一个使得每个仓库中每个对象都独一无二的 keyPath 。在这个示例中的属性是“ssn”,因为社会安全号码被确保是唯一的。被存储在该仓库中的所有对象都必须存在“ssn”。

        我们也请求了一个名为“name”的着眼于存储的对象的 name 属性的索引。如同 createObjectStore(),createIndex() 提供了一个可选地 options 对象,该对象细化了我们希望创建的索引类型。新增一个不带 name 属性的对象也会成功,但是这个对象不会出现在 "name" 索引中。

        我们现在可以使用存储的用户对象的 ssn 直接从对象存储空间中把它们提取出来,或者通过使用索引来使用他们的 name 进行提取。

3.6 使用键生成器

        在创建对象仓库时设置 autoIncrement 标记会为该仓库开启键生成器。默认该设置是不开启的。

        使用键生成器,当你向对象仓库新增记录时键会自动生成。对象仓库生成的键往往从 1 开始,然后自动生成的新的键会在之前的键的基础上加 1。生成的键的值从来不会减小,除非数据库操作结果被回滚,比如,数据库事务被中断。因此删除一条记录,甚至清空对象仓库里的所有记录都不会影响对象仓库的键生成器。

        我们可以使用键生成器创建一个对象仓库:

// 打开 indexedDB.
var request = indexedDB.open(dbName, 3);

request.onupgradeneeded = function (event) {

    var db = event.target.result;

    // 设置 autoIncrement 标志为 true 来创建一个名为 names 的对象仓库
    var objStore = db.createObjectStore("names", { autoIncrement : true });

    // 因为 names 对象仓库拥有键生成器,所以它的键会自动生成。
    // 被插入的数据可以表示如下:
    // key : 1 => value : "Bill"
    // key : 2 => value : "Donna"
    customerData.forEach(function(customer) {
        objStore.add(customer.name);
    });
};

四、增加、读取和删除数据

        需要开启一个事务才能对你的创建的数据库进行操作。事务来自于数据库对象,而且你必须指定你想让这个事务跨越哪些对象仓库。一旦你处于一个事务中,你就可以目标对象仓库发出请求。你要决定是对数据库进行更改还是只需从中读取数据。事务提供了三种模式:readonly、readwrite 和 versionchange。

        想要修改数据库模式或结构——包括新建或删除对象仓库或索引,只能在 versionchange 事务中才能实现。该事务由一个指定了 version 的 IDBFactory.open 方法启动。(在仍未实现最新标准的 WebKit 浏览器,IDBFactory.open 方法只接受一个参数,即数据库的 name,这样你必须调用 IDBVersionChangeRequest.setVersion 来建立 versionchange 事务。

        使用 readonly 或 readwrite 模式都可以从已存在的对象仓库里读取记录。但只有在 readwrite 事务中才能修改对象仓库。你需要使用 IDBDatabase.transaction (en-US) 启动一个事务。该方法接受两个参数:storeNames (作用域,一个你想访问的对象仓库的数组),事务模式 mode(readonly 或 readwrite)。该方法返回一个包含 IDBIndex.objectStore (en-US) 方法的事务对象,使用 IDBIndex.objectStore (en-US) 你可以访问你的对象仓库。未指定 mode 时,默认为 readonly 模式。

备注: 从 Firfox 40 起,IndexedDB 事务放松了对持久性的保证以提高性能(参见 Bug1112702)以前在 readwrite 事务中,只有当所有的数据确保被写入磁盘时才会触发 IDBTransaction.oncomplete (en-US)。在 Firefox 40+ 中,当操作系统被告知去写入数据后 complete 事件便被触发,但此时数据可能还没有真正的写入磁盘。complete 事件触发因此变得更快,但这样会有极小的机会发生以下情况:如果操作系统崩溃或在数据被写入磁盘前断电,那么整个事务都将丢失。由于这种灾难事件是罕见的,大多数使用者并不需要过分担心。如果由于某些原因你必须确保数据的持久性(例如你要保存一个无法再次计算的关键数据),你可以使用实验性(非标准的)readwriteflush 模式来创建事务以强制 complete 事件在数据写入磁盘后触发(查看 IDBDatabase.transaction (en-US))。

你可以通过使用合适的作用域和模式来加速数据库访问,这有两个提示:

  • 定义作用域时,只指定你用到的对象仓库。这样,你可以同时运行多个不含互相重叠作用域的事务。
  • 只在必要时指定 readwrite 事务。你可以同时执行多个 readnoly 事务,哪怕它们的作用域有重叠;但对于在一个对象仓库上你只能运行一个 readwrite 事务。了解更多,请查看基本概念 (en-US)中事务 (en-US)的定义。

4.1 向数据库中增加数据

        如果你刚刚创建了一个数据库,你可能想往里面写点东西。看起来会像下面这样:

var transaction = db.transaction(["customers"], "readwrite");
// 注意:旧的实验性接口实现使用了常量 IDBTransaction.READ_WRITE 而不是 "readwrite"。
// 如果你想支持这样旧版本的实现,你只要这样写就可以了:
// var transaction = db.transaction(["customers"], IDBTransaction.READ_WRITE);

        transaction() 方法接受两个参数(一个是可选的)并返回一个事务对象。第一个参数是事务希望跨越的对象存储空间的列表。如果你希望事务能够跨越所有的对象存储空间你可以传入一个空数组,但请不要这样做,因为标准规定传入一个空数组会导致一个 InvalidAccessError(可以使用 (en-US)属性db.objectStoreNames (en-US))。如果你没有为第二个参数指定任何内容,你得到的是只读事务。如果你想写入数据,你需要传入 "readwrite" 标识。

        现在我们已经有了一个事务,我们需要理解它的生命周期。事务和事件循环的联系非常密切。如果你创建了一个事务但是并没有使用它就返回给事件循环,那么事务将会失活。保持事务活跃的唯一方法就是在其上构建一个请求。当请求完成时你将会得到一个 DOM 事件,并且,假设请求成功了,你将会有另外一个机会在回调中来延长这个事务。如果你没有延长事务就返回到了事件循环,那么事务将会变得不活跃,依此类推。只要还有待处理的请求事务就会保持活跃。事务生命周期真的很简单但是可能需要一点时间你才能对它变得习惯。还有就是来几个例子也会有所帮助。如果你开始看到 TRANSACTION_INACTIVE_ERR 错误代码,那么你已经把某些事情搞乱了。

        事务接收三种不同的 DOM 事件:error、abort 和 complete。我们已经提及 error 事件是冒泡机制,所以事务会接收由它产生的所有请求所产生的错误。更微妙的一点,错误会中断它所处的事务。除非你在错误发生的第一时间就调用了 stopPropagation 并执行了其他操作来处理错误,不然整个事务将会回滚。这种机制迫使你考虑和处理错误场景,如果觉得细致的错误处理太繁琐,你可以在数据库上添加一个全局的错误处理。如果你在事务中没有处理一个已发生的错误或者调用 abort 方法,那么该事务会被回滚,并触发 abort 事件。另外,在所有请求完成后,事务的 complete 事件会被触发。如果你进行大量数据库操作,跟踪事务而不是具体的请求会使逻辑更加清晰。

        现在你拥有了一个事务,你需要从中取出一个对象仓库。你只能在创建事务时指定的对象仓库中取出一个对象仓库。然后你可以添加任何你需要的数据。

// 在所有数据添加完毕后的处理
transaction.oncomplete = function(event) {
  alert("All done!");
};

transaction.onerror = function(event) {
  // 不要忘记错误处理!
};

var objectStore = transaction.objectStore("customers");
customerData.forEach(function(customer) {
  var request = objectStore.add(customer);
  request.onsuccess = function(event) {
    // event.target.result === customer.ssn;
  };
});

        调用 call() 方法产生的请求的 result 是被添加的数据的键。所以在该例中,它应该全等于被添加对象的 ssn 属性,因为对象仓库使用 ssn 属性作为键路径(key path)。注意,add() 方法的调用时,对象仓库中不能存在相同键的对象。如果你想修改一个已存在的条目,或者你不关心该数据是否已存在,你可以使用 put() 方法,就像下面 更新数据库中的记录 模块所展示的。

4.2  从数据库中删除数据

        如下例所示:

var request = db.transaction(["customers"], "readwrite")
                .objectStore("customers")
                .delete("444-44-4444");

request.onsuccess = function(event) {
  // 删除成功!
};

4.3 从数据库中获取数据

        现在数据库里已经有了一些信息,你可以通过几种方法对它进行提取。首先是简单的 get()。你需要提供键来提取值,代码如下:

var transaction = db.transaction(["customers"]);
var objectStore = transaction.objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function(event) {
  // 错误处理!
};
request.onsuccess = function(event) {
  // 对 request.result 做些操作!
  alert("Name for SSN 444-44-4444 is " + request.result.name);
};

注意,你可以通过限制事务的作用域和模式来加速数据库访问。这里有两个提示:

  • 定义作用域时,只指定你用到的对象仓库。这样,你可以同时运行多个不含互相重叠作用域的事务。
  • 只在必要时指定 readwrite 事务。你可以同时执行多个 readnoly 事务,哪怕它们的作用域有重叠;但对于在一个对象仓库上你只能运行一个 readwrite 事务。了解更多,请查看基本概念 (en-US)中事务 (en-US)的定义。

4.4 更新数据库中的记录

        现在修改一下并把它插回数据库的操作时非常简单的,代码如下所示:

var objectStore = db.transaction(["customers"], "readwrite").objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function(event) {
  // 错误处理
};
request.onsuccess = function(event) {
  // 获取我们想要更新的数据
  var data = event.target.result;

  // 更新你想修改的数据
  data.age = 42;

  // 把更新过的对象放回数据库
  var requestUpdate = objectStore.put(data);
   requestUpdate.onerror = function(event) {
     // 错误处理
   };
   requestUpdate.onsuccess = function(event) {
     // 完成,数据已更新!
   };
};

备注: In this case we've had to specify a readwrite transaction because we want to write to the database, not just read from it.在这个例子中我们必须指定一个 readwrite 事务,因为我们想要写入一个数据库,而不仅仅是从中读取。

4.5 使用游标

        使用 get() 要求你知道你想要检索哪一个键。如果你想要遍历对象存储空间中的所有值,那么你可以使用游标。代码示例如下:

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() 函数需要几个参数。首先,你可以使用一个 key range 对象来限制被检索的项目的范围。第二,你可以指定你希望进行迭代的方向。在上面的示例中,我们在以升序迭代所有的对象。游标成功的回调有点特别。游标对象本身是请求的 result (上面我们使用的是简写形式,所以是 event.target.result)。然后实际的 key 和 value 可以根据游标对象的 key 和 value 属性被找到。如果你想要保持继续前行,那么你必须调用游标上的 continue() 。当你已经到达数据的末尾时(或者没有匹配 openCursor() 请求的条目)你仍然会得到一个成功回调,但是 result 属性是 undefined。

        使用游标的一种常见模式是提取出在一个对象存储空间中的所有对象然后把它们添加到一个数组中,像这样:

var customers = [];

objectStore.openCursor().onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    customers.push(cursor.value);
    cursor.continue();
  }
  else {
    alert("以获取所有客户信息:" + customers);
  }
};

备注: 可选地,你可以使用 getAll() 来处理这种情况(以及 getAllKeys())。下面的代码的效果和上例相同:

objectStore.getAll().onsuccess = function(event) {
  alert("Got all customers: " + event.target.result);
};

查看游标的 value 属性会带来性能消耗,因为对象是被懒生成的。当你使用 getAll() ,浏览器必须一次创建所有的对象。如果你仅仅想检索 m 键,那么使用游标将比使用 getAll() 高效得多。当然如果你想获取一个由对象仓库中所有对象组成的数组,请使用 getAll()。

4.6 使用索引

使用 SSN 作为键来存储客户数据是合理的,因为 SSN 唯一地标识了一个个体(对隐私来说这是否是一个好的想法是另外一个话题,不在本文的讨论范围内)。如果你想要通过姓名来查找一个客户,那么,你将需要在数据库中迭代所有的 SSN 直到你找到正确的那个。以这种方式来查找将会非常的慢,相反你可以使用索引。

// 首先,确定你已经在 request.onupgradeneeded 中创建了索引:
// objectStore.createIndex("name", "name");
// 否则你将得到 DOMException。

var index = objectStore.index("name");

index.get("Donna").onsuccess = function(event) {
  alert("Donna's SSN is " + event.target.result.ssn);
};

        “name”游标不是唯一的,因此 name 被设成 "Donna" 的记录可能不止一条。在这种情况下,你总是得到键值最小的那个。

        如果你需要访问带有给定 name 的所有的记录你可以使用一个游标。你可以在索引上打开两个不同类型的游标。一个常规游标映射索引属性到对象存储空间中的对象。一个键索引映射索引属性到用来存储对象存储空间中的对象的键。不同之处被展示如下:

index.openCursor().onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // cursor.key 是一个 name,就像 "Bill", 然后 cursor.value 是整个对象。
    alert("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);
    cursor.continue();
  }
};

index.openKeyCursor().onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // cursor.key 是一个 name,就像 "Bill", 然后 cursor.value 是那个 SSN。
    // 没有办法可以得到存储对象的其余部分。
    alert("Name: " + cursor.key + ", SSN: " + cursor.value);
    cursor.continue();
  }
};

4.7 指定游标的范围和方向

        如果你想要限定你在游标中看到的值的范围,你可以使用一个 key range 对象然后把它作为第一个参数传给 openCursor() 或是 openKeyCursor()。你可以构造一个只允许一个单一 key 的 key range,或者一个具有下限或上限,或者一个既有上限也有下限。边界可以是“闭合的”(也就是说 key range 包含给定的值)或者是“开放的”(也就是说 key range 不包括给定的值)。这里是它如何工作的:

// 仅匹配 "Donna"
var singleKeyRange = IDBKeyRange.only("Donna");

// 匹配所有超过“Bill”的,包括“Bill”
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");

// 匹配所有超过“Bill”的,但不包括“Bill”
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);

// 匹配所有不超过“Donna”的,但不包括“Donna”
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);

// 匹配所有在“Bill”和“Donna”之间的,但不包括“Donna”
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);

// 使用其中的一个键范围,把它作为 openCursor()/openKeyCursor 的第一个参数
index.openCursor(boundKeyRange).onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // 当匹配时进行一些操作
    cursor.continue();
  }
};

        有时候你可能想要以倒序而不是正序(所有游标的默认顺序)来遍历。切换方向是通过传递 prev 到 openCursor() 方法来实现的:

objectStore.openCursor(boundKeyRange, "prev").onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // 进行一些操作
    cursor.continue();
  }
};

        如果你只是想改变遍历的方向,而不想对结果进行筛选,你只需要给第一个参数传入 null。

objectStore.openCursor(null, "prev").onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // Do something with the entries.
    cursor.continue();
  }
};

        因为“name”索引不是唯一的,那就有可能存在具有相同 name 的多条记录。要注意的是这种情况不可能发生在对象存储空间上,因为键必须永远是唯一的。如果你想要在游标在索引迭代过程中过滤出重复的,你可以传递 nextunique (或 prevunique 如果你正在向后寻找)作为方向参数。当 nextunique 或是 prevunique 被使用时,被返回的那个总是键最小的记录。

index.openKeyCursor(null, IDBCursor.nextunique).onsuccess = function(event) {
  var cursor = event.target.result;
  if (cursor) {
    // Do something with the entries.
    cursor.continue();
  }
};

五、IDBObjectStore属性

5.1 autoIncrement

        IDBObjectStore的只读属性autoIncrement接口返回当前objectStore的自增标记值(true或false)。

        什么是自增?熟悉SQL的朋友应该知道,SQL数据里面字段可以设置自增,当一条记录被 插入时,不必传入该字段,新记录的该字段值会在前面一条记录该字段值的基础上加1,而IndexedDB里面的autoIncrement也是同样功能。

5.2 indexNames

        IDBObjectStore的只读属性indexNames返回此对象存储中对象的indexes名称(name)列表。

5.3 keyPath

        IDBObjectStore的只读属性keyPath接口返回当前objectstore的key path。

        什么是keyPath,在IndexedDB中,一条记录就是一个objecct,object里面有一个属性作为这条记录的主要依据用来进行查询 ,而这个属性的属性名就是keyPath,属性值就是key。

        add、put方法都可以传第二个参数,当前的objectStore的autoIncrement为true时,你一般不会设置keyPath,如果这个时间你在put的时候不提供第二个参数,indexedDB就不知道要更新哪一条记录了。

5.4 name

        IDBObjectStore的name属性指此对象存储区的名称。

异常:

尝试更改对象存储区域名称时,可能会出现以下几种情况。

1、无效的状态错误(InvalidStateError )

        如果对象存储已被删除或当前事务不是升级事务,则抛出;只能在升级事务期间重命名索引;也就是说,当模式为versionchange时。

2、事务未使用的错误(TransactionInactiveError )

        如果当前事务未使用,则抛出。

3、约束错误(ConstraintError )

        如果对象存储区已经在使用指定的名称,则抛出。

5.5 transaction

         IDBObjectStore的事务只读属性返回该对象存储所属的事务对象。

六、IDBObjectStore方法

6.1 add()

        IDBObjectStore的add()方法返回一个IDBRequest对象,并在一个单独的线程中创建该值的结构化克隆,并将克隆的值存储在对象存储中。这是为了向对象存储区添加新记录。

        确定添加操作是否成功完成,可以在IDBObjectStore之外监听事务的完成事件。添加请求的成功事件,因为在成功事件触发后,事务仍然可能失败。换言而之,成功事件只在事务成功排除时触发。

        add()方法是一个只插入的方法。如果一个记录已存在于对象存储中,并以key参数作为其键,则在返回的请求对象上触发一个错误的ConstrainError事件。对于更新现有记录,您应该使用IDBObjectStore的put()方法代替。        

语法:

add('value');

add('value', key);

参数:

名称 描述
value 要存储的值
key 用户识别的键。如果未指定,则结果为null。

异常:

此方法可能引发以下类型之一的DOMException:

1、只读错误(ReadOnlyError)

        如果与此操作相关联的事务处于只读模式,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

3、数据错误(DataError )

        如果适用以下任何条件,则抛出:

  • 对象存储区使用内嵌键或有一个键生成器,并提供了一个键参数。
  • 对象存储使用离线键,没有键生成器,也没有提供键参数。
  • 对象存储使用内嵌键但没有键生成器,而且对象存储的键路径不会产生有效的键。
  • 提供了键参数,但不包含有效的键。

4、无效的状态错误(InvalidStateError )

        如果IDBObjectStore已被删除或移除,则抛出。

5、数据克隆错误(DataCloneError )

        如果所存储的数据不能被内部结构化克隆算法克隆,则抛出。

6、约束错误(ConstraintError )

        如果由于违反主键约束(由于已有记录具有相同的主键值)而导致插入操作失败,则抛出。

6.2 clear()

        IDBObjectStore的clear()方法创建并立即返回IDBRequest对象,并在单独的线程中清除该对象,并在单独的线程中清除该对象存储。这用于对象存储中删除所有当前数据。

        清除对象存储包括从对象存储中删除所有记录和从引用对象存储的索引中删除所有记录。要删除存储中的部分记录,可以使用IDBObjectStore.delete传递一个键或IDBKeyRange。

语法:

clear()

参数:

        无

返回值:

        返回一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

1、只读错误(ReadOnlyError)

        如果与此操作相关联的事务处于只读模式,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

6.3 count()

        IDBObjectStore的count()方法返回一个IDBRequest对象,并且在一个单独的线程中返回与提供的键或IDBKeyRange匹配的记录总数。如果不提供参数,则返回存储中的记录总数。

语法:

count()        //返回所有记录总数

count(query)    //返回匹配记录总数

参数:

        一个键或IDBKeyRange对象,它指定要计数的记录范围。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

此方法可能引发以下类型的DOM异常。

1、无效的状态错误(InvalidStateError )

        如果IDBObjectStore已被删除或移除,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

3、数据错误(DataError )

        如果指定的键或键范围无效,则抛出。

6.4 createIndex()

        IDBObjectStore的createIndex()方法在连接的数据库中创建并返回一个新的IDBIndex对象。它创建一个新的字段/列,为要包含的每个数据库记录定义一个新的数据点。

        请记住,IndexedDB索引可以包含任何JavaScript数据类型;IndexedDB使用结构化克隆算法来序列化存储的对象,这允许存储简单对象和复杂对象。

注意,此方法只能从VersionChange事务模式回调调用。

语法:

createIndex(indexName, keyPath)
createIndex(indexName, keyPath, objectParameters)

参数:

名称 描述
indexName 创建的索引的名称。注意,可以创建一个空名称的索引。
keyPath 索引要使用的键路径。注意,可以使用空keyPath创建索引,也可以将序列(数组)作为keyPath传入。
objectParameters

一个可以包含以下属性的对象:

unique:如果为true,索引将不允许单个键的重复值。

multiEntry:如果为true,则当keyPath解析为数组时,索引将在每个数组元素的索引中添加一个条目。如果为false,它将添加包含数组的单个条目。

locale :目前仅支持firefox(43+),这允许您为索引指定语言环境。然后,通过键范围对数据执行的任何排序操作都将遵守该语言环境的排序规则(参见语言环境感知排序)。你可以用以下三种方式指定它的值:

  • 字符串:包含特定地区代码的字符串,例如en-US或pl。
  • auto:将使用平台默认语言环境(可能由用户代理设置更改)。
  • null或undefined:如果没有指定语言环境,将使用正常的JavaScript排序—不支持语言环境。

返回值:

        IDBIndex对象:新创建的索引。

异常:

此方法可能引发以下类型之一的DOMException:

1、约束错误(ConstraintError )

        如果数据库中已经存在具有相同名称的索引,则引发。索引名区分大小写。

2、无效的访问错误(InvalidAccessError )

        如果提供的键路径是一个序列,并且在objectParameters对象中将multiEntry设置为true,则抛出。

3、无效的状态错误(InvalidStateError )

  • 该方法不是从versionchange事务模式回调调用的,即从onupgradeneeded处理程序内部调用。
  • 对象存储已被删除。

4、语法错误(SyntaxError )

        如果提供的keyPath不是有效的键路径,则抛出。

5、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore所属的事务不活动(例如已被删除或删除),则抛出。在版本41之前的Firefox中,在这种情况下也会引发InvalidStateError,这是具有误导性的;这个问题现在已经修复

6.5 delete()

        IDBObjectStore的delete()方法返回一个IDBRequest对象,并在一个单独的线程中删除指定的记录。

        可以传递键或IDBKeyRange,允许从存储中删除一条或多条记录。要删除存储中的所有记录,使用IDBObjectStore.clear。

        请记住,如果使用IDBCursor,可以使用IDBCursor.delete()方法更有效地删除当前记录——而不必显式查找记录的键。

语法:

delete(key)

参数:

名称 描述
key 要删除的记录的键,或用于删除范围内键的所有记录的IDBKeyRange。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

该方法可能引发以下类型的DOMException:

1、事务未使用错误(TransactionInactiveError )

        如果此对象存储库的事务不活动,则抛出。

2、只读错误(ReadOnlyError)

        如果对象存储的事务模式为只读,则抛出。

3、无效的状态错误(InvalidStateError )

        如果对象存储已被删除,则抛出。

4、数据错误(DataError )

        如果键不是有效的键或键范围,则返回。

6.6 deleteIndex()

         IDBObjectStore接口的deleteIndex()方法销毁连接的数据库中具有指定名称的索引,该索引在版本升级期间使用。

        注意,此方法只能从VersionChange事务模式回调调用。此方法同步修改IDBObjectStore的indexNames属性。

语法:

deleteIndex(indexName)

参数:

名称 描述
indexName 要删除的现有索引的名称。

异常:

1、无效的状态错误(InvalidStateError )

        如果没有从版本更改事务模式回调调用该方法,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore所属的事务不活动(例如已被删除或删除),则抛出。

3、未找到错误(NotFoundError )

        如果数据库中没有具有给定名称(区分大小写)的索引,则抛出。

6.7 get()

         IDBObjectStore的get()方法返回一个IDBRequest对象,并且在一个单独的线程中返回由指定键选择的对象存储。这是从对象存储中检索特定的记录。

        如果成功找到一个值,则创建该值的结构化克隆,并将其设置为请求对象的结果。

注意:该方法对以下两种情况产生相同的结果:

a) 数据库中不存在的记录;

b) 具有未定义值的记录。为了区分这些情况,使用相同的键调用openCursor()方法。如果记录存在,该方法提供一个游标,如果记录不存在,则不提供游标。

语法:

get(key)

参数:

名称 描述
key 标识要检索的记录的键或键范围。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

此方法可能引发以下类型之一的DOMException:

1、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

2、数据错误(DataError )

        如果提供的键或键范围包含无效键则抛出。

3、无效的状态错误(InvalidStateError )

        如果IDBObjectStore已被删除或移除,则抛出。

6.8 getAll()

         IDBObjectStore接口的getAll()方法返回一个IDBRequest对象,该对象包含对象存储中与指定参数匹配的所有对象,如果没有给出参数,则返回存储中的所有对象。

        如果成功找到一个值,则创建该值的结构化克隆,并将其设置为请求对象的结果。

此方法产生相同的结果:

  • 数据库中不存在的记录
  • 具有未定义值的记录

要区分这些情况,你要么回调:

  1. 使用相同键的openCursor()方法。如果记录存在,该方法提供一个游标,如果记录不存在,则不提供游标。
  2. 具有相同键的count()方法,如果行存在,该方法将返回1,如果行不存在,则返回0。

语法:

getAll()
getAll(query)
getAll(query, count)

参数:

名称 描述
query 要查询的键或IDBKeyRange。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。
count 指定如果找到多个值要返回的值的数量。如果它小于0或大于2^32 - 1,则会抛出TypeError异常。

返回值:

 返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

此方法可能引发以下类型之一的DOMException:

1、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

2、数据错误(DataError )

        如果提供的键或键范围包含无效键或为空,则抛出。

3、无效的状态错误(InvalidStateError )

        如果IDBObjectStore已被删除或移除,则抛出。

4、类型错误(TypeError)

        如果count参数不在0和2^32 - 1之间,则抛出。

6.9 getAllKeys()

         IDBObjectStore的getAllKeys()方法返回一个IDBRequest对象,该对象检索对象存储中匹配指定参数的所有对象的记录键,如果没有给出参数,则检索存储中所有对象的记录键。

        如果成功找到一个值,则创建该值的结构化克隆,并将其设置为请求对象的结果。

此方法产生相同的结果:

  • 数据库中不存在的记录
  • 具有未定义值的记录

        为了区分这些情况,需要使用相同的键调用openCursor()方法。如果记录存在,该方法提供一个游标,如果记录不存在,则不提供游标。

语法:

getAllKeys()
getAllKeys(query)
getAllKeys(query, count)

参数:

名称 描述
query 是或解析为IDBKeyRange的值。
count 指定如果找到多个值要返回的值的数量。如果它小于0或大于2^32 - 1,则会抛出TypeError异常。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

此方法可能引发以下类型之一的DOMException:

1、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

2、数据错误(DataError )

        如果提供的键或键范围包含无效键或为空,则抛出。

3、无效的状态错误(InvalidStateError )

        如果IDBObjectStore已被删除或移除,则抛出。

6.10 getKey()

         IDBObjectStore的getKey()方法返回一个IDBRequest对象,并在一个单独的线程中返回指定查询选择的键。这是从对象存储中检索特定的记录。

        如果成功找到一个键,则创建该键的结构化克隆,并将其设置为请求对象的结果。

语法:

getKey(key)

参数:

名称 描述
key 标识要检索的记录的键或键范围。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

此方法可能引发以下类型之一的DOMException:

1、无效的状态错误(InvalidStateError )

        如果IDBObjectStore已被删除或移除,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

3、数据错误(DataError )

        如果提供的键或键范围包含无效键,则抛出。

6.11 index()

        IDBObjectStore的index()方法在当前对象存储中打开一个命名索引,在此之后可以使用它,例如,使用游标返回按该索引排序的一系列记录。

语法:

index(name)

参数:

名称 描述
name 要打开的索引的名称。

返回值:

        用于访问索引的IDBIndex对象。

异常:

1、无效的状态错误(InvalidStateError )

        如果源对象存储区已被删除,或对象存储区的事务已完成,则抛出。

2、未找到错误(NotFoundError )

        如果数据库中没有具有给定名称(区分大小写)的索引,则抛出。

6.12 openCursor()

        IDBObjectStore接口的openCursor()方法返回一个IDBRequest对象,并且在一个单独的线程中返回一个新的IDBCursorWithValue对象。用于使用游标在对象存储区中迭代。

        要确定添加操作是否成功完成,请监听结果的success事件。

语法:

openCursor()
openCursor(query)
openCursor(query, direction)

参数:

名称 描述
query 要查询的键或IDBKeyRange。如果传递了一个有效键,则默认为只包含该键的范围。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。
direction

指示光标移动方向的字符串。默认值是next。有效值:

next:游标在存储的开始处打开;然后,光标按键的递增顺序返回所有记录,甚至是重复的记录。

nextunique:游标在存储的开始处打开;然后,游标按键的递增顺序返回所有非重复的记录。

prev:游标在存储的开始处打开;然后,光标按键的递减顺序返回所有记录,甚至是重复的记录。

prevunique:游标在存储的开始处打开;然后,游标按键的递减顺序返回所有不重复的记录。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

 此方法可能引发以下类型之一的DOMException:

1、无效的状态错误(InvalidStateError )

        如果此IDBObjectStore或IDBIndex已被删除,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

3、数据错误(DataError )

        如果指定的键或键范围无效,则抛出。

6.13 openKeyCursor()

        IDBObjectStore的openKeyCursor()方法返回一个IDBRequest对象,其结果将被设置为一个IDBCursor,可用于迭代匹配结果。用于用光标遍历对象存储区的键。

        要确定添加操作是否成功完成,请监听结果的success事件。

语法:

openKeyCursor()
openKeyCursor(query)
openKeyCursor(query, direction)

参数:

名称 描述
query 要查询的密钥范围。如果传递了一个有效键,则默认为只包含该键的范围。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。
direction IDBCursorDirection指示光标移动的方向。有效值为“next”、“nextunique”、“prev”和“prevunique”。默认为“next”。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

此方法可能引发以下类型之一的DOMException:

1、无效的状态错误(InvalidStateError )

        如果此IDBObjectStore或IDBIndex已被删除,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

3、数据错误(DataError )

        如果指定的键或键范围无效,则抛出。

6.14 put()

        IDBObjectStore的put()方法更新数据库中给定的记录,如果给定的项不存在,则插入一个新记录。

        它返回一个IDBRequest对象,并在一个单独的线程中创建该值的结构化克隆,并将克隆的值存储在对象存储区中。这用于在事务模式为读写时添加新记录,或更新对象存储中的现有记录。如果成功存储了记录,则在返回的请求对象上触发一个成功事件,并将结果设置为所存储记录的键,将事务设置为打开该对象存储的事务。

        put方法是一个更新或插入方法。看到IDBObjectStore。为仅插入方法添加方法。

        请记住,如果您有一个指向您想要更新的记录的IDBCursor,那么使用IDBCursor.update()更新它要优于使用IDBObjectStore.put()。这样做可以清楚地表明将更新现有记录,而不是插入新记录。

语法:

put(item)
put(item, key)

参数:

名称 描述
item 您希望更新(或插入)的项。
key 你想要更新的记录的主键(例如从IDBCursor.primaryKey)。这仅用于具有autoIncrement主键的对象存储,因此键不在记录对象的字段中。在这种情况下,调用put(item)总是会插入一个新记录,因为它不知道您可能想要修改什么现有记录。

 返回值:

一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

异常:

此方法可能引发以下类型之一的DOMException:

1、只读错误(ReadOnlyError)

        如果与此操作相关联的事务处于只读模式,则抛出。

2、事务未使用错误(TransactionInactiveError )

        如果此IDBObjectStore的事务不活动,则抛出。

3、数据错误(DataError )

        如果适用以下任何条件,则抛出:

  • 对象存储区使用内嵌键或有一个键生成器,并提供了一个键参数。
  • 对象存储使用离线键,没有键生成器,也没有提供键参数。
  • 对象存储使用内嵌键但没有键生成器,而且对象存储的键路径不会产生有效的键。
  • 提供了键参数,但不包含有效的键。

4、无效的状态错误(InvalidStateError )

        如果IDBObjectStore已被删除或移除,则抛出。

5、数据克隆错误(DataCloneError )

        如果所存储的数据不能被内部结构化克隆算法克隆,则抛出。

猜你喜欢

转载自blog.csdn.net/jiciqiang/article/details/127276773