本指南介绍了
IndexedDB API。
我们使用的是 Jake Archibald
IndexedDB Promise
库,后者与 IndexedDB API 非常相似,但使用的是 promise,
您可以await
以获得更简洁的语法。这简化了 API
以保持其结构不变。
什么是 IndexedDB?
IndexedDB 是一个大规模的 NoSQL 存储系统,该系统允许仅 用户浏览器中的任何内容除了通常的搜索、get 和 IndexedDB 还支持事务,并且非常适合 存储大量结构化数据。
每个 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 数据库应该为每种类型创建一个对象存储
需要持久保留的数据例如,
个人资料和备注可能具有包含 person
的 people
对象存储
对象以及包含 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()
方法
对象存储的实例将新索引的名称作为第一个
参数,而第二个参数是指要
索引中。在最后一个参数中,您可以定义两个选项,
索引包括 unique
和 multiEntry
。如果将 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 中的所有数据操作都在事务内执行。每个 操作的形式如下:
- 获取数据库对象。
- 在数据库上打开事务。
- 打开事务的对象存储。
- 对对象存储执行操作。
事务可以看作是操作或组的安全封装容器 操作。如果事务中的某个操作失败,则所有 操作会被回滚。事务特定于一个或多个对象存储, 您在打开交易时定义的值。它们可以是只读的,也可以是只读的 和写入。这表示事务中的操作是否读取了 或对数据库进行更改
创建数据
如需创建数据,请调用 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:
- 正在添加美味三明治记录。
- 正在添加一条鸡蛋记录。
- 表明交易已完成的信号 (
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
循环中,
可以读取光标的当前位置的 key
和 value
属性,以及
您可以采用最适合自己业务的方式
应用。准备就绪后,您就可以调用 cursor
对象的 continue()
。
方法转到下一行,并且 while
循环会在光标悬停时终止
到达数据集的末尾。
将游标与范围和索引搭配使用
借助索引,您可以通过非
主键。您可以针对任何属性创建索引,相应属性会成为 keyPath
。
针对索引指定范围,并获取
使用 getAll()
或游标指定范围。
使用 IDBKeyRange
对象定义范围。以及以下任意一项
方法:
upperBound()
。lowerBound()
。bound()
(两者都有)。only()
。includes()
。
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
。当此代码
则浏览器尚不存在数据库,因此 oldVersion
为 0
,且 switch
语句从 case 0
开始。在此示例中,
向数据库添加 'store'
对象存储。
要点:在 switch
语句中,每个 case
后通常会有一个 break
块,但此处特意不使用它。这样,如果现有
如果数据库版本落后几个版本,或者如果数据库不存在,则代码
case
块的其余部分,直到最新状态为止。因此,在此示例中,
浏览器会继续执行,直到 case 1
,name
会在
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');
}
}
});
如果您在上一个示例中创建的数据库仍存在于浏览器中,
时,oldVersion
为 2
。浏览器会跳过 case 0
并
case 1
,然后执行 case 2
中的代码,这会创建一个 description
索引中。之后,浏览器会拥有一个版本 3 的数据库,其中包含 store
具有 name
和 description
索引的对象存储。
深入阅读
以下资源提供了有关使用 IndexedDB 的更多信息和上下文。