使用 IndexedDB

本指南介绍了 IndexedDB API。 我们使用的是 Jake Archibald IndexedDB Promise 库,后者与 IndexedDB API 非常相似,但使用的是 promise, 您可以await 以获得更简洁的语法。这简化了 API 以保持其结构不变。

IndexedDB 是一个大规模的 NoSQL 存储系统,该系统允许仅 用户浏览器中的任何内容除了通常的搜索、get 和 IndexedDB 还支持事务,并且非常适合 存储大量结构化数据。

每个 IndexedDB 数据库都是唯一的源站 (通常为网站域名或子域名),这意味着相应网站无法访问或访问 其他来源。它的数据存储限制 通常很大(如果存在),但是不同的浏览器会处理限制 和数据驱逐不同如需了解更多详情,请参阅深入阅读部分。 。

数据库
IndexedDB 的最高级别。它包含对象存储,而后者又包含 包含要保留的数据您可以使用 无论您选择什么名称。
对象存储
用于存储数据的单个存储分区,类似于关系型数据库中的表。 通常,每种类型(不是 JavaScript 数据)都有一个对象存储 特定类型)的数据。与数据库表不同,JavaScript 数据 即存储区中的数据类型不需要保持一致。例如,如果应用 有一个 people 对象存储区,其中包含有关三个人的信息,分别是 用户的年龄属性可以是 53'twenty-five'unknown
索引
一种对象存储,用于整理另一个对象存储(称为 引用对象存储)。索引将使用 按此属性检索对象存储中的记录。例如,如果你 存储人员时,您可能希望稍后按姓名、年龄或 您最爱的动物。
操作
与数据库的交互。
事务
一个或一组操作的封装容器,用于确保数据库 完整性。如果事务中的某个操作失败,则所有操作都不是 并且数据库会恢复到事务之前的状态 开始了IndexedDB 中的所有读取或写入操作都必须是事务的一部分。 这允许原子化读取-修改-写入操作,而不会出现冲突的风险 其他线程可同时对数据库执行操作。
Cursor
一种用于迭代数据库中多条记录的机制。

如何检查是否支持 IndexedDB

IndexedDB 几乎受到普遍支持。 不过,如果您使用的是旧版浏览器 功能检测支持。最简单的方法是查看window 对象:

function indexedDBStuff () {
  // Check for IndexedDB support:
  if (!('indexedDB' in window)) {
    // Can't use IndexedDB
    console.log("This browser doesn't support IndexedDB");
    return;
  } else {
    // Do IndexedDB stuff here:
    // ...
  }
}

// Run IndexedDB code:
indexedDBStuff();

如何打开数据库

借助 IndexedDB,您可以使用自己选择的任何名称创建多个数据库。如果 但当您尝试打开某个数据库时却发现它不存在自动创建。 如需打开数据库,请使用 idb 库中的 openDB() 方法:

import {openDB} from 'idb';

async function useDB () {
  // Returns a promise, which makes `idb` usable with async-await.
  const dbPromise = await openDB('example-database', version, events);
}

useDB();

此方法会返回一个解析为数据库对象的 promise。使用 openDB() 方法,请提供名称、版本号和要设置的事件对象 启动数据库

以下是上下文中的 openDB() 方法示例:

import {openDB} from 'idb';

async function useDB () {
  // Opens the first version of the 'test-db1' database.
  // If the database does not exist, it will be created.
  const dbPromise = await openDB('test-db1', 1);
}

useDB();

请将对 IndexedDB 支持的检查置于匿名函数的顶部。这个 如果浏览器不支持 IndexedDB,则退出该函数。如果该函数可以 继续,它会调用 openDB() 方法来打开名为 'test-db1' 的数据库。 本例中省略了可选的 event 对象 很简单,但您需要指定它才能使用 IndexedDB 执行任何有意义的操作。

如何使用对象存储

一个 IndexedDB 数据库包含一个或多个对象存储,每个对象存储都有一个 一列用于输入键,在另一列中输入与该键关联的数据。

创建对象存储

结构合理的 IndexedDB 数据库应该为每种类型创建一个对象存储 需要持久保留的数据例如, 个人资料和备注可能具有包含 personpeople 对象存储 对象以及包含 note 对象的 notes 对象存储区。

为了确保数据库的完整性,您只能在 openDB() 调用中的事件对象。事件对象公开了一个 upgrade() 方法,您可以用它来创建对象存储。调用 createObjectStore() 方法(位于 upgrade() 方法中),以便创建对象存储:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('example-database', 1, {
    upgrade (db) {
      // Creates an object store:
      db.createObjectStore('storeName', options);
    }
  });
}

createStoreInDB();

此方法采用对象存储的名称和可选配置 对象,您可以定义对象存储的各种属性。

以下示例展示了如何使用 createObjectStore()

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db1', 1, {
    upgrade (db) {
      console.log('Creating a new object store...');

      // Checks if the object store exists:
      if (!db.objectStoreNames.contains('people')) {
        // If the object store does not exist, create it:
        db.createObjectStore('people');
      }
    }
  });
}

createStoreInDB();

在此示例中,系统向 openDB() 方法传递了一个事件对象以创建 与之前一样,对象存储的创建工作 (位于事件对象的 upgrade() 方法中)。不过,由于浏览器会抛出一个 错误,我们建议您 将 createObjectStore() 方法封装在用于检查的 if 语句中 对象存储是否存在。在 if 代码块内,调用 使用 createObjectStore() 创建一个名为 'firstOS' 的对象存储。

如何定义主键

定义对象存储时,您可以定义在 存储数据。您可以通过以下任一方式来定义主键: 密钥路径或使用密钥生成器的方法。

键路径是始终存在的属性,包含唯一值。对于 例如,对于 people 对象存储,您可以选择电子邮件地址 用作密钥路径:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }
    }
  });
}

createStoreInDB();

本示例创建了名为 'people' 的对象存储,并将 email 属性设为 keyPath 选项的主键。

您还可以使用密钥生成器,例如 autoIncrement。密钥生成器 会为添加到对象存储的每个对象创建一个唯一值。默认情况下 如果未指定键,IndexedDB 会创建一个键并将其单独存储 。

以下示例创建了一个名为 'notes' 的对象存储,并将 以自动递增数字的形式自动分配主键:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

以下示例与上一个示例类似,但这次 自动递增值已明确分配给名为 'id' 的属性。

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

选择使用哪种方法来定义密钥取决于您的数据。如果您的 数据的属性始终具有唯一性,您可以将其设为 keyPath, 强制实施这种唯一性否则,请使用自动递增值。

以下代码将创建三个对象存储,演示 在对象存储中定义主键:

import {openDB} from 'idb';

async function createStoresInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }

      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }

      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoresInDB();

如何定义索引

索引是一种对象存储,用于从引用中检索数据。 对象存储。索引位于引用对象内 存储并包含相同的数据,但将指定属性用作 键路径,而不是引用存储区的主键。在以下情况下,必须 创建对象存储,并且可用于为 数据。

如需创建索引,请调用 createIndex() 方法:

import {openDB} from 'idb';

async function createIndexInStore() {
  const dbPromise = await openDB('storeName', 1, {
    upgrade (db) {
      const objectStore = db.createObjectStore('storeName');

      objectStore.createIndex('indexName', 'property', options);
    }
  });
}

createIndexInStore();

该方法会创建并返回索引对象。createIndex() 方法 对象存储的实例将新索引的名称作为第一个 参数,而第二个参数是指要 索引中。在最后一个参数中,您可以定义两个选项, 索引包括 uniquemultiEntry。如果将 unique 设置为 true, 索引不允许单个键有重复的值。下一条:multiEntry 确定当编入索引的属性为数组时 createIndex() 的行为。如果 设置为 true,则 createIndex() 会为每个数组在索引中添加一个条目 元素。否则,它会添加一个包含该数组的条目。

示例如下:

import {openDB} from 'idb';

async function createIndexesInStores () {
  const dbPromise = await openDB('test-db3', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });

        peopleObjectStore.createIndex('gender', 'gender', { unique: false });
        peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
      }

      if (!db.objectStoreNames.contains('notes')) {
        const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });

        notesObjectStore.createIndex('title', 'title', { unique: false });
      }

      if (!db.objectStoreNames.contains('logs')) {
        const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createIndexesInStores();

在此示例中,'people''notes' 对象存储具有索引。接收者 创建索引,首先分配 createObjectStore() 的结果(一个对象 存储对象)添加到变量中,以便对其调用 createIndex()

如何使用数据

本部分介绍如何创建、读取、更新和删除数据。这些 操作都是异步的,使用 promise(如果 IndexedDB API 使用 请求。这简化了 API。您无需监听 请求之后,您可以对从 ViewModel 返回的数据库对象调用 .then() openDB() 方法启动与数据库的交互,或 await 其 创建过程。

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

  1. 获取数据库对象。
  2. 在数据库上打开事务。
  3. 打开事务的对象存储。
  4. 对对象存储执行操作。

事务可以看作是操作或组的安全封装容器 操作。如果事务中的某个操作失败,则所有 操作会被回滚。事务特定于一个或多个对象存储, 您在打开交易时定义的值。它们可以是只读的,也可以是只读的 和写入。这表示事务中的操作是否读取了 或对数据库进行更改

创建数据

如需创建数据,请调用 add() 方法,并传入要添加的数据。add() 方法的第一个参数是要向其添加数据的对象存储,而 第二个参数是一个对象,其中包含您需要的字段和相关数据。 添加。以下是最简单的示例,其中添加了一行数据:

import {openDB} from 'idb';

async function addItemToStore () {
  const db = await openDB('example-database', 1);

  await db.add('storeName', {
    field: 'data'
  });
}

addItemToStore();

每个 add() 调用都发生在事务内,因此即使 promise 解析 但这并不一定意味着操作有效。为了确保 因此,您需要检查整个 系统已使用 transaction.done() 方法完成交易。这是一个 promise 会在事务自行完成时解析,并会在事务完成时拒绝 处理记录错误。您必须对所有“写入”执行此检查运维套件, 因为只有这样才能知道数据库的更改 情况。

以下代码展示了如何在事务中使用 add() 方法:

import {openDB} from 'idb';

async function addItemsToStore () {
  const db = await openDB('test-db4', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('foods')) {
        db.createObjectStore('foods', { keyPath: 'name' });
      }
    }
  });
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Add multiple items to the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.add({
      name: 'Sandwich',
      price: 4.99,
      description: 'A very tasty sandwich!',
      created: new Date().getTime(),
    }),
    tx.store.add({
      name: 'Eggs',
      price: 2.99,
      description: 'Some nice eggs you can cook up!',
      created: new Date().getTime(),
    }),
    tx.done
  ]);
}

addItemsToStore();

打开数据库(并根据需要创建对象存储)后,您需要 通过对事务调用 transaction() 方法来打开事务。此方法 使用您要进行交易的商店及模式的参数。 在本例中,我们希望向商店写入数据。因此,此示例 指定 'readwrite'

下一步是开始将商品作为交易的一部分添加到商店。 在前面的示例中,我们处理了对 'foods' 执行的三项操作。 存储每个查询都会返回一个 promise:

  1. 正在添加美味三明治记录。
  2. 正在添加一条鸡蛋记录。
  3. 表明交易已完成的信号 (tx.done)。

由于所有这些操作都基于 promise,因此我们需要等待所有 把它们说完就结束将这些 promise 传递给 Promise.all 这是一种很好的符合人体工程学的方法。Promise.all 接受 promise 并在传递给它的所有 promise 都解析完成后完成。

对于所添加的两条记录,事务实例的 store 接口 调用 add() 并向其传递数据。您可以await Promise.all 通话 以便在事务完成时完成

读取数据

如需读取数据,请调用 get() 方法。openDB() get() 会获取存储区的名称以及您所需的对象的主键值 要检索的数据。下面是一个基本示例:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('example-database', 1);

  // Get a value from the object store by its primary key value:
  const value = await db.get('storeName', 'unique-primary-key-value');
}

getItemFromStore();

add() 一样,get() 方法会返回一个 promise,因此您可以在发生以下情况时对其执行 await 操作: 您也可以使用 promise 的 .then() 回调。

以下示例对 'test-db4' 数据库的 get() 方法 'foods' 对象存储,用于按 'name' 主键获取单个行:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('test-db4', 1);
  const value = await db.get('foods', 'Sandwich');

  console.dir(value);
}

getItemFromStore();

从数据库中检索单个行相当简单: 数据库,并指定要创建的行的对象存储区和主键值 希望从中获取数据由于 get() 方法会返回 promise,因此您可以 await

更新数据

如需更新数据,请调用 put() 方法。put() 方法类似于 add() 方法 还可代替 add() 来创建数据。下面是一个基本示例 使用 put() 按主键值更新对象存储中的行:

import {openDB} from 'idb';

async function updateItemInStore () {
  const db = await openDB('example-database', 1);

  // Update a value from in an object store with an inline key:
  await db.put('storeName', { inlineKeyName: 'newValue' });

  // Update a value from in an object store with an out-of-line key.
  // In this case, the out-of-line key value is 1, which is the
  // auto-incremented value.
  await db.put('otherStoreName', { field: 'value' }, 1);
}

updateItemInStore();

与其他方法一样,此方法会返回 promise。您还可以将 put() 用作 交易的一部分。下面是一个使用上文中 'foods' 存储区的示例 更新三明治和鸡蛋价格的操作:

import {openDB} from 'idb';

async function updateItemsInStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Update multiple items in the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.put({
      name: 'Sandwich',
      price: 5.99,
      description: 'A MORE tasty sandwich!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.store.put({
      name: 'Eggs',
      price: 3.99,
      description: 'Some even NICER eggs you can cook up!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.done
  ]);
}

updateItemsInStore();

项的更新方式取决于您设置键的方式。如果您设置了 keyPath, 对象存储中的每一行都与一个内嵌键相关联。上述 示例根据此键更新行, 在这种情况下,您需要指定该键来更新 对象存储。您还可以通过设置 autoIncrement 作为主键。

删除数据

如需删除数据,请调用 delete() 方法:

import {openDB} from 'idb';

async function deleteItemFromStore () {
  const db = await openDB('example-database', 1);

  // Delete a value 
  await db.delete('storeName', 'primary-key-value');
}

deleteItemFromStore();

add()put() 一样,您可以将其用作事务的一部分:

import {openDB} from 'idb';

async function deleteItemsFromStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Delete multiple items from the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.delete('Sandwich'),
    tx.store.delete('Eggs'),
    tx.done
  ]);
}

deleteItemsFromStore();

数据库交互的结构与 操作。请务必在交易完成之前 将 tx.done 方法添加到您传递给 Promise.all 的数组中。

获取所有数据

到目前为止,您一次只能从存储区中检索一个对象。您还可以 使用 getAll() 方法或游标。

getAll() 方法

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

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('storeName');

  console.dir(allValues);
}

getAllItemsFromStore();

此方法会返回对象存储中的所有对象,没有任何约束条件 无所谓。这是从对象存储中获取所有值的最直接方式, 也是最不灵活的。

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('foods');

  console.dir(allValues);
}

getAllItemsFromStore();

此示例对 'foods' 对象存储调用 getAll()。此操作会返回 'foods' 中的对象,按主键排序。

如何使用游标

游标是一种更灵活的检索多个对象的方式。光标选择 或逐个建立索引,从而使您能够执行某些操作, 之后的数据与其他数据库操作一样,游标 都是在事务中发挥作用的

如需创建游标,请调用 openCursor() 作为事务的一部分在对象存储上执行。通过以下平台使用 'foods' 商店: 之前的示例就是如何在导航中让游标前进 对象存储:

import {openDB} from 'idb';

async function getAllItemsFromStoreWithCursor () {
  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');

  // Open a cursor on the designated object store:
  let cursor = await tx.store.openCursor();

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

getAllItemsFromStoreWithCursor();

在本例中,交易在 'readonly' 模式下打开, 调用 openCursor 方法。在后续的 while 循环中, 可以读取光标的当前位置的 keyvalue 属性,以及 您可以采用最适合自己业务的方式 应用。准备就绪后,您就可以调用 cursor 对象的 continue()。 方法转到下一行,并且 while 循环会在光标悬停时终止 到达数据集的末尾。

将游标与范围和索引搭配使用

借助索引,您可以通过非 主键。您可以针对任何属性创建索引,相应属性会成为 keyPath。 针对索引指定范围,并获取 使用 getAll() 或游标指定范围。

使用 IDBKeyRange 对象定义范围。以及以下任意一项 方法:

upperBound()lowerBound() 方法指定上限和下限 范围。

IDBKeyRange.lowerBound(indexKey);

或者:

IDBKeyRange.upperBound(indexKey);

它们各自接受一个参数:所需项的索引 keyPath 值 指定为上限或下限

bound() 方法会同时指定上限和下限:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

这些函数的范围默认包含在内,这意味着它包括 指定为范围限制的数据。要省去这些值 通过将 true 作为第二个参数传递,将范围指定为独占 lowerBound()upperBound(),或者作为 bound()(分别表示下限和上限)。

下一个示例使用 'foods' 对象中 'price' 属性的索引 商店。现在,存储区还附加了一个表单,其中包含两个针对 该范围的上限和下限。使用以下代码查找包含 介于这两个限制之间的价格:

import {openDB} from 'idb';

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

  let range;

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

  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');
  const index = tx.store.index('price');

  // Open a cursor on the designated object store:
  let cursor = await index.openCursor(range);

  if (!cursor) {
    return;
  }

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

// Get items priced between one and four dollars:
searchItems(1.00, 4.00);

示例代码首先获取限值,并检查限制 存在。下一个代码块会决定使用哪种方法来限制范围 进行微调。在数据库交互中,打开 事务,然后在对象存储中打开 'price' 索引。通过 'price' 索引可让您按价格搜索内容。

然后,该代码会在索引上打开一个光标并传入范围。光标 返回一个表示范围内第一个对象的 promise,或者,如果 undefined 范围内没有任何数据。cursor.continue() 方法会返回一个 该游标表示下一个对象,然后继续循环,直到 直至达到该范围的结束值。

数据库版本控制

调用 openDB() 方法时,您可以指定数据库版本号 。在本指南的所有示例中, 设置为 1,但如果需要,您可以将数据库升级到新版本, 以某种方式修改它。如果指定的版本高于 现有数据库,系统会执行事件对象中的 upgrade 回调, 让您可以将新的对象存储和索引添加到数据库中。

upgrade 回调中的 db 对象具有特殊的 oldVersion 属性, ,用于表示浏览器可访问的数据库的版本号。 您可以将此版本号传递到 switch 语句中,以执行 基于现有数据库版本的 upgrade 回调内的代码 数字。示例如下:

import {openDB} from 'idb';

const db = await openDB('example-database', 2, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');
    }
  }
});

此示例将数据库的最新版本设置为 2。当此代码 则浏览器尚不存在数据库,因此 oldVersion0,且 switch 语句从 case 0 开始。在此示例中, 向数据库添加 'store' 对象存储。

要点:在 switch 语句中,每个 case 后通常会有一个 break 块,但此处特意不使用它。这样,如果现有 如果数据库版本落后几个版本,或者如果数据库不存在,则代码 case 块的其余部分,直到最新状态为止。因此,在此示例中, 浏览器会继续执行,直到 case 1name 会在 store 对象存储。

要在 'store' 对象存储中创建 'description' 索引,请更新 版本号,并添加新的 case 块,如下所示:

import {openDB} from 'idb';

const db = await openDB('example-database', 3, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');

      case 2:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('description', 'description');
    }
  }
});

如果您在上一个示例中创建的数据库仍存在于浏览器中, 时,oldVersion2。浏览器会跳过 case 0case 1,然后执行 case 2 中的代码,这会创建一个 description 索引中。之后,浏览器会拥有一个版本 3 的数据库,其中包含 store 具有 namedescription 索引的对象存储。

深入阅读

以下资源提供了有关使用 IndexedDB 的更多信息和上下文。

IndexedDB 文档

数据存储限制