发布于 2026-01-05 12 阅读
0

如何使用 IndexedDB 为 Web 应用程序存储本地数据

如何使用 IndexedDB 为 Web 应用程序存储本地数据

如果您想 fork 这个项目,代码和教程可以在Github上找到。

如果您对本教程的视频版本感兴趣,请点击下方链接。您可以一边观看视频一边对照这篇博客中的代码进行操作。(视频完全是可选的,博客文章中已经包含了所有步骤和说明。)

IndexedDB教程

  1. 什么是 IndexedDB?
  2. 重要术语
  3. 如何使用 IndexedDB
  4. 浏览您的数据库
  5. 更新和删除数据
  6. 局限性
  7. 进一步学习
  8. 总结

什么是 IndexedDB?

IndexedDB 是一个浏览器内数据库,可用于存储大量数据,以支持您的网页或 Web 应用程序。信息以简单的键值对形式存储,类似于您可能已经熟悉的 JavaScript 对象存储方式。

如果您只是想寻找一种最简单的方法来存储用户端的数据,并且这些数据在浏览器刷新和关闭后仍然保留,那么使用简单的本地存储API 可能更适合您。本地存储支持最大 5MB 的数据,支持简单的键值对,例如 JavaScript 对象。

但是,如果您有兴趣了解一种功能更强大、特性更丰富的客户端存储方法,该方法支持与真正的完整数据库系统相同的许多特性,那么 IndexedDB 可能就是您的正确选择。

IndexedDB 受大多数现代浏览器支持,允许您存储高达用户可用硬盘空间 50% 的数据(超过此限制浏览器将开始丢弃数据)。要更好地了解 IndexedDB 的存储和空间限制,MDN 提供了一个很好的资源

重要术语

数据库

数据库是组织有序的数据集合。与此类似,电脑硬盘可能经过优化,用于存储大量杂乱无章、偶尔才会被搜索的数据,而数据库则假定数据会被频繁搜索,并经过优化以确保这些搜索尽可能快。

模式

数据库的模式指的是数据的结构。例如,在我们的示例中,我们将使用一个用于跟踪汽车信息的数据库可以想象,与汽车相关的信息数不胜数:颜色、品牌、型号、车况、配置、车辆识别码 (VIN)、年份等等。

我们的模式定义了哪些属性会被跟踪并存储在数据库中。因此,在我们的示例中,我们只使用了颜色品牌这两个属性。我们还有一个ID值,用于在数据库中标识对象。

指数

数据库术语中的索引就像书末的索引一样。它本质上是一个从一组值到另一组值的映射。

书末的索引就像一张图表,将词语与页码对应起来。它能帮助读者快速找到所需的内容,而无需逐页翻阅。

计算机的原理也完全相同。当查找一个庞大的数据库时,如果没有索引,搜索将从头开始,遍历所有数据,直到找到目标为止。添加索引会在内存中创建一个结构,使查找过程更快更便捷。索引会占用内存空间,因此通常被认为是空间和速度之间的一种权衡。在大多数情况下,这种权衡是值得的。

数据库中索引最常见的用途是指向主键,主键是数据库中存储项的唯一标识(例如 ID 号)。对于汽车来说,主键可能是车辆识别码 (VIN);对于书籍来说,主键可能是 ISBN,等等。

交易

在数据库上下文中,事务是一个或多个必须全部成功执行的操作,否则任何一个操作都不会执行。

要理解为什么需要交易,最常见的例子就是在银行数据库的账户之间转账。转账操作包括remove money从一个账户add money转到另一个账户。如果add money操作由于任何原因失败,你也需要remove money操作本身失败,否则就会出现非常糟糕的情况:钱就这么“消失了”。

光标

游标代表您在数据库查看数据时的当前位置。IndexedDB 中的游标可用于整个对象存储,甚至可以用于仅限于特定类型文档的索引。它们允许您在数据库中逐个文档迭代,而无需查询所有数据并将其存储在客户端应用程序(在本例中为我们的 JavaScript 程序)的内存中

如何使用 IndexedDB



// 1
const indexedDB =
  window.indexedDB ||
  window.mozIndexedDB ||
  window.webkitIndexedDB ||
  window.msIndexedDB ||
  window.shimIndexedDB;

if (!indexedDB) {
  console.log("IndexedDB could not be found in this browser.");
}

// 2
const request = indexedDB.open("CarsDatabase", 1);


Enter fullscreen mode Exit fullscreen mode
  1. 根据您使用的浏览器不同,IndexedDB 可能使用不同的名称。幸运的是,它们都使用相同的 API,因此程序会遍历所有可能的路径,直到找到一个存在的索引并将其保存到 indexedDB 变量中。如果找不到,则会向日志发送一条消息,并且后续代码将执行失败。

  2. 向数据库版本 1发出“打开”请求。第一个参数是数据库名称,第二个参数是版本号。如果您以后决定更新数据库结构,可以递增此数字,以确保所有用户都使用最新版本的数据库架构。

接下来,我们需要监听发起请求时可能发生的不同事件success。这些事件包括 `onRequest` 、` erroronRequestEffect` 和upgradeneeded`onRequestFailure`。我们先来处理错误情况:

错误事件



request.onerror = function (event) {
  console.error("An error occurred with IndexedDB");
  console.error(event);
};


Enter fullscreen mode Exit fullscreen mode

您最可能遇到的错误是,如果您在浏览器的隐私模式或隐身模式下运行程序,IndexedDB 可能不受支持。如果您正在使用 IndexedDB,请务必禁用这些模式。

需要升级事件

当数据库版本号递增或创建新数据库时,将触发此事件。

每当出现这种情况时,您都需要定义数据库的结构。所以我们在这里进行定义:



request.onupgradeneeded = function () {
  //1
  const db = request.result;

  //2
  const store = db.createObjectStore("cars", { keyPath: "id" });

  //3
  store.createIndex("cars_colour", ["colour"], { unique: false });

  // 4
  store.createIndex("colour_and_make", ["colour", "make"], {
    unique: false,
  }); 
};


Enter fullscreen mode Exit fullscreen mode

我们将逐行分析,以便理解每个部分:

  1. 请求的结果是数据库对象本身。我们目前位于onupgradeneeded事件内部,因此可以假定数据库存在,否则该onerror函数应该已经触发。

  2. IndexedDB 使用对象存储的概念。对象存储本质上是数据集合的名称。您可以在单个数据库中创建任意数量的对象存储。如果您使用过其他数据库,可以将它们想象成集合keyPath。`field_name`是 IndexedDB 用于标识对象的字段名称。通常,这是一个唯一的数字。

    您还可以autoincrement: true手动设置一个唯一 ID,这样就无需您自己手动设置了。您插入的第一个项目的 ID 为id0,第二个项目的 IDid为 1,依此类推。

    我们将以汽车为例,所以我将我的对象存储命名为cars

  3. 添加索引使我们能够根据特定术语(除了定义为值之外keyPath)在对象存储中搜索。此索引将允许我们按属性搜索汽车对象colour(请原谅加拿大式拼写)。

  4. 类似地,您可以创建所谓的复合索引,这种索引可以使用多个词项的组合进行查找。在本例中,它允许我们通过品牌和颜色查找汽车。

现在我们已经建立了数据库模式,可以开始添加数据并进行查询了。这可以在数据库打开后进行,事件触发时会确认数据库已打开success



request.onsuccess = function () {
  console.log("Database opened successfully");

  const db = request.result;

  // 1
  const transaction = db.transaction("cars", "readwrite");

  //2
  const store = transaction.objectStore("cars");
  const colourIndex = store.index("cars_colour");
  const makeModelIndex = store.index("colour_and_make");

  //3
  store.put({ id: 1, colour: "Red", make: "Toyota" });
  store.put({ id: 2, colour: "Red", make: "Kia" });
  store.put({ id: 3, colour: "Blue", make: "Honda" });
  store.put({ id: 4, colour: "Silver", make: "Subaru" });

  //4
  const idQuery = store.get(4);
  const colourQuery = colourIndex.getAll(["Red"]);
  const colourMakeQuery = makeModelIndex.get(["Blue", "Honda"]);

  // 5
  idQuery.onsuccess = function () {
    console.log('idQuery', idQuery.result);
  };
  colourQuery.onsuccess = function () {
    console.log('colourQuery', colourQuery.result);
  };
  colourMakeQuery.onsuccess = function () {
    console.log('colourMakeQuery', colourMakeQuery.result);
  };

  // 6
  transaction.oncomplete = function () {
    db.close();
  };
};


Enter fullscreen mode Exit fullscreen mode
  1. 为了对数据库执行任何操作,我们必须创建一个事务。一个事务可以包含单个操作,也可以包含多个操作,但所有操作都必须成功,否则任何操作都不会成功。接下来,我们将逐一向数据库添加四辆“汽车”,但如果其中任何一项插入操作因任何原因失败,则所有四项操作都会失败,因为它们都发生在我们创建的这个事务中。

  2. 这里我们需要获取指向存储车辆数据的对象存储库的引用。我们还需要获取指向索引的引用。这些引用实际上就是指向我们在上一节中在数据库中创建的值的引用。

  3. 对象存储中的方法put是我们向数据库添加数据的方式。基于我们创建的模式,我们将添加一批对象(汽车)。我给它们分配的 ID 只是一个唯一的数字,您也可以使用之前在创建对象存储时描述的自增值,这样就无需手动设置此值。

  4. 这些是我们的查询语句。您可以像第一行那样,直接使用值查询项目keyPath。第二行我们使用了一个getAll方法,该方法会返回一个包含所有结果的数组。我们正在索引中搜索cars_colour“红色”。我们应该会找到两个结果。最后一行在我们的复合索引中搜索颜色为“蓝色”且品牌为“本田”的车辆,结果只有一个。

  5. 这些是success事件处理程序,它们会在查询完成后触发并执行其中的代码。它们只有result在查询结果被填充后才会触发,因此可以安全地检查查询结果,就像我们在这些函数中通过将结果记录到控制台来做的那​​样。

  6. 最后,由于这是我们唯一的操作,事务完成后我们将关闭与数据库的连接。使用 IndexedDB 时,无需手动触发事务,它会自动运行。

如果你把上面每一段代码(示例中的每个代码块)分别复制到一个.js文件中,然后在浏览器中运行(关闭隐私/隐身模式),结果会如下所示。记下每个与我们查询内容匹配的日志值。

IndexedDB 示例

浏览您的数据库

浏览器让查看商店内容变得非常简单。首先,使用以下命令打开开发者控制台F12

Application在 Chrome 浏览器中,您可以在-> Storage->下找到它IndexedDB

IndexedDB Chrome

在 Firefox 浏览器中,它位于Storage->下Indexed DB

IndexedDB Firefox

更新和删除数据

更新

首先,你需要获取要更新的数据get,然后使用put数据存储中的方法来更新现有记录。Put方法是一个“插入更新”方法,它要么覆盖现有数据,要么在数据不存在时插入新数据。



const subaru = store.get(4);

subaru.onsuccess= function () {
  subaru.result.colour = "Green";
  store.put(subaru.result);
}


Enter fullscreen mode Exit fullscreen mode

这将把数据库中银色斯巴鲁的颜色更新为绿色。

消除

IndexedDB 中的数据可以通过与查询类似的 API 进行删除。最简单的方法是直接通过已知的键删除条目:



const deleteCar = store.delete(1);

deleteCar.onsuccess = function () {
  console.log("Red Toyota has been removed");
};


Enter fullscreen mode Exit fullscreen mode

如果您不知道键值,但想根据某个索引的值删除元素,也可以这样做:



const redCarKey = colourIndex.getKey(["Red"]);

redCarKey.onsuccess = function () {
  const deleteCar = store.delete(redCarKey.result);

  deleteCar.onsuccess = function () {
    console.log("Red car has been removed");
  };
};


Enter fullscreen mode Exit fullscreen mode

(如果您想在初始示例项目中尝试这些功能,可以将这些代码片段粘贴到此行代码之前。)



transaction.oncomplete = function () {


Enter fullscreen mode Exit fullscreen mode

你的结果将是:

更新 IndexedDB 数据

局限性

使用 IndexedDB 时需要注意一些限制。

首先,这一点适用于你可能使用的任何客户端存​​储解决方案,因为你绝不应该依赖它的存在来保证应用程序的正常运行。请记住,用户可以随时清除他们的私人数据和存储空间。你保存的任何数据都应该是应用程序的补充,并且在删除后易于恢复。

第二点与性能有关。IndexedDB 在单次事务中插入大量数据时速度很快,但如果这些插入/更新操作跨越多个事务进行,速度就会显著下降。

解决方案很简单,只需意识到这个限制,并确保在开发应用程序时尽可能将数据修改批量处理成最少的事务即可。如果无法做到这一点,请花时间研究并考虑 IndexedDB 是否适合您的项目。市面上还有其他替代方案。

进一步学习

IndexedDB 的功能远不止这篇入门教程所涵盖的。例如,如果您打算存储大量数据(可能超过某些用户单次查询能够存储在内存中的数据量),那么您就会对游标的概念感兴趣。

javascript.info 和 MDN 都对 IndexedDB 进行了非常深入的介绍,如果您想深入了解 IndexedDB,可以查看它们:

总结

请查看我的其他学习教程。如果您觉得其中任何教程有用,欢迎留言或提问,也欢迎分享给其他人:


想看更多类似教程,请在推特上关注我@eagleson_alex

文章来源:https://dev.to/alexeagleson/how-to-use-indexeddb-to-store-data-for-your-web-application-in-the-browser-1o90