# 浏览器存储-IndexedDB

IndexedDB 是一个浏览器内置的数据库,它比 localStorage 强大得多。

  • 通过支持多种类型的键,来存储几乎可以是任何类型的值。
  • 支撑事务的可靠性。
  • 支持键范围查询、索引。
  • 和 localStorage 相比,它可以存储更大的数据量。

对于传统的 客户端-服务器 应用,这些功能通常是没有必要的。IndexedDB 适用于离线应用,可与 ServiceWorkers 和其他技术相结合使用。

# 打开数据库

要想使用 IndexedDB,首先需要 open(连接)一个数据库。

let openRequest = indexedDB.open(name, version);
  • name —— 字符串,即数据库名称。
  • version —— 一个正整数版本,默认为 1,用于版本控制 s

调用之后会返回 openRequest 对象,我们需要监听该对象上的事件:

  • success:数据库准备就绪,openRequest.result 中有了一个数据库对象“Database Object”,使用它进行进一步的调用。
  • error:打开失败。
  • upgradeneeded:数据库已准备就绪,但其版本已过时(见下文)。

IndexedDB 具有内建的“模式(scheme)版本控制”机制,这在服务器端数据库中是不存在的。

与服务器端数据库不同,IndexedDB 存在于客户端,数据存储在浏览器中。因此,开发人员无法随时都能访问它。因此,当我们发布了新版本的应用程序,用户访问我们的网页,我们可能需要更新该数据库。

如果本地数据库版本低于 open 中指定的版本,会触发一个特殊事件 upgradeneeded。我们可以根据需要比较版本并升级数据结构。

当数据库还不存在时(从技术上讲,该版本为 0),也会触发 upgradeneeded 事件。因此,我们可以执行初始化。

# 版本控制

假设我们发布了应用程序的第一个版本。

let openRequest = indexedDB.open("store", 1);

openRequest.onupgradeneeded = function () {
  // 如果客户端没有数据库则触发
  // ...执行初始化...
};
openRequest.onsuccess = function () {
  let db = openRequest.result;
  // 继续使用 db 对象处理数据库
};

之后不久,我们发布了第二个版本。

let openRequest = indexedDB.open("store", 2);

// 现有的数据库版本小于 2(或不存在)
openRequest.onupgradeneeded = function (event) {
  let db = openRequest.result;
  switch (
    event.oldVersion // 现有的 db 版本
  ) {
    case 0:
    // 版本 0 表示客户端没有数据库
    // 执行初始化
    case 1:
    // 客户端版本为 1
    // 更新
  }
};
openRequest.onsuccess = function () {
  let db = openRequest.result;
  // 继续使用 db 对象处理数据库 此时数据库为版本2
};

删除数据库

let deleteRequest = indexedDB.deleteDatabase(name);
// 然后可以使用deleteRequest.onsuccess/onerror 追踪(tracks)结果

# 对象库

要在 IndexedDB 中存储某些内容,我们需要一个 对象库。

对象库是 IndexedDB 的核心概念,在其他数据库中对应的对象称为“表”或“集合”。

IndexedDB 使用 标准序列化算法 来克隆和存储对象。类似于 JSON.stringify,不过功能更加强大,能够存储更多的数据类型。

有一种对象不能被存储:循环引用的对象。此类对象不可序列化,也不能进行 JSON.stringify。

库中的每个值都必须有唯一的键 key。

db.createObjectStore(name[, keyOptions]);
  • name 是存储区名称,例如 "books" 表示书。
  • keyOptions 是具有以下两个属性之一的可选对象

如果我们不提供 keyOptions,那么以后需要在存储对象时,显式地提供一个键。

db.createObjectStore("books", { keyPath: "id" });

删除对象库:

db.deleteObjectStore("books");

# 事务

术语“事务”是通用的,许多数据库中都有用到。

事务是一组操作,要么全部成功,要么全部失败。

例如,当一个人买东西时,我们需要:

从他们的账户中扣除这笔钱。 将该项目添加到他们的清单中。 如果完成了第一个操作,但是出了问题,比如停电。这时无法完成第二个操作,这非常糟糕。两件时应该要么都成功(购买完成,好!)或同时失败(这个人保留了钱,可以重新尝试)。

事务可以保证同时完成。

启动事务:

db.transaction(store[, type]);
  • store 是事务要访问的库名称,例如 "books"。如果我们要访问多个库,则是库名称的数组。
  • type – 事务类型,分为 readonly 和 readwrite,其中前者性能更高,允许多个事务同时访问同一存储区

创建事务后,我们可以将项目添加到库,就像这样:

let transaction = db.transaction("books", "readwrite"); // (1)

// 通过事务获取对象库进行操作
let books = transaction.objectStore("books"); // (2)

let book = {
  id: "js advanced",
  price: 10,
  created: new Date()
};

let request = books.add(book); // (3)

request.onsuccess = function () {
  // (4)
  console.log("Book added to the store", request.result);
};

request.onerror = function () {
  console.log("Error", request.error);
};

上面我们只监听的一个数据操作,为了检测事务成功完成,们可以监听 transaction.oncomplete 事件:

let transaction = db.transaction("books", "readwrite");

// ……执行操作……

transaction.oncomplete = function () {
  console.log("Transaction is complete"); // 事务执行完成
};