IndexedDB: Web浏览器中的本地数据库
介绍
IndexedDB是一种在Web浏览器中使用的本地数据库技术,它的用途广泛而多样。通过IndexedDB,Web应用可以网络连接的情况下,在客户端存储和检索大量结构化数据。
主要特点包括:
- 数据存储:IndexedDB允许创建多个数据库,每个数据库可以包含多个对象存储空间,类似于关系数据库中的表格。数据以键值对的形式存储,可以灵活地存储各种类型的数据。
- 异步操作:IndexedDB的API设计为异步执行,通过使用回调函数、事件监听或Promise等方式处理结果。这种异步模型可以避免阻塞主线程,提高应用程序的性能和响应能力。
- 支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
与客户端存储方式对比
以下是LocalStorage、Cookies、内存和IndexedDB这四种客户端存储方式的比较:
存储方式 | 存储容量 | 数据持久性 | 数据访问速度 | 数据存储位置 | 使用场景 |
---|---|---|---|---|---|
LocalStorage | 5MB - 10MB | 持久性 | 快 | 浏览器本地 | 存储小型数据、会话信息、持久性缓存等 |
Cookies | 4KB | 持久性 | 较慢 | 浏览器本地 | 存储小型数据、会话信息、跨域通信等 |
内存 | 取决于设备 | 临时性 | 非常快 | 浏览器内存 | 临时数据存储、运行时缓存、临时状态管理等 |
IndexedDB | 取决于浏览器 | 持久性 | 中等 | 浏览器本地 | 存储大量结构化数据、离线应用、复杂数据查询等 |
数据库操作
创建和打开数据库
废话不多说,我们直接来创建一个数据库。
1.使用IndexedDB API中的open()
方法打开(创建)数据库连接。此方法接受两个参数:数据库名称和版本号。
const request = indexedDB.open('myDatabase', 1);
2.当数据库打开成功后,将触发success
事件,可以在该事件的处理程序中进行后续操作。
request.onsuccess = function(event) {
const db = event.target.result;
// 进行数据库操作,如添加数据、查询数据等
};
3.如果打开数据库时发生错误,将触发error
事件,可以在该事件的处理程序中处理错误情况。
request.onerror = function(event) {
console.log('Failed to open database');
};
版本管理和升级
当你需要修改数据库的结构,比如添加新的对象存储空间、修改现有的对象存储空间或索引等,就需要通过升级版本来执行这些变更操作。
当打开数据库时,如果指定的版本号高于现有数据库的版本号(在open()
方法上指定版本),将触发upgradeneeded
事件。在该事件的处理程序中,可以执行数据库结构和数据的变更操作。
request.onupgradeneeded = function(event) {
const db = event.target.result;
// 创建新的对象存储空间或修改现有的对象存储空间
const objectStore = db.createObjectStore('myObjectStore', { keyPath: 'id' });
// 创建新的索引或修改现有的索引
objectStore.createIndex('nameIndex', 'name', { unique: false });
};
如果在数据库升级过程中发生错误,将触发blocked
事件。该事件表示有其他连接正在使用数据库,导致升级被阻塞。在这种情况下,你需要关闭其他连接才能继续升级。
request.onblocked = function(event) {
console.log('Another connection is using the database');
};
删除数据库
使用indexedDB.deleteDatabase
方法来删除数据库。该方法接受要删除的数据库名称作为参数,并返回一个IDBRequest对象,表示删除操作的异步请求。
// 删除数据库
const deleteRequest = indexedDB.deleteDatabase("myDatabase");
对象存储空间(ObjectStore)
什么是对象存储空间?
在 IndexedDB 中,对象存储空间(Object Store)是用于存储和管理数据对象的地方。它类似于关系型数据库中的表或文档数据库中的集合,但它是基于键值对的存储模型。
对象存储空间的创建和配置
使用 createObjectStore()
方法来创建对象存储空间,它接受两个参数:对象存储空间的名称和配置选项。
keyPath
参数指定了对象的键路径,这里我们使用自增的主键 "id"。通过设置 autoIncrement
为 true
,每次添加新的对象时,会自动为其生成一个唯一的自增值。
// 创建对象存储空间
const objectStore = db.createObjectStore("users", { keyPath: "id", autoIncrement: true });
// 定义对象存储空间的属性,确保邮箱不会重复,所以我们使用 unique
objectStore.createIndex("nameIndex", "name", { unique: false });
objectStore.createIndex("ageIndex", "age", { unique: false });
objectStore.createIndex("emailIndex", "email", { unique: true });
为对象存储空间创建了三个索引:nameIndex
、ageIndex
和 emailIndex
,分别用于姓名、年龄和电子邮件地址的索引。
数据的增删改查操作
你需要开启一个事务才能对你的创建的数据库进行操作。一旦你处于一个事务中,你就可以目标对象仓库发出请求。
通过db.transaction
来创建一个事务,接受两个参数,第一个参数是要查询或者修改的对象存储空间数组,第二个参数是选择事务的模式,取决于你是要对数据库进行更改还是只需从中读取数据。事务提供了三种模式:readonly
、readwrite
和 versionchange
。
// 获取事务
const transaction = db.transaction(["users"], "readwrite");
//根据事务获取对应得存储空间
const objectStore = transaction.objectStore("users");
请注意,IndexedDB 中的事务不需要显式地调用 commit()
方法来提交更改。事务会自动提交,除非遇到错误或显式地调用 abort()
方法来中止事务。
以下是 IndexedDB 常用的增删改查操作的 API
操作 | API | 说明 |
---|---|---|
添加数据 | objectStore.add(data) | 向对象存储空间中添加数据 |
读取数据 | objectStore.get(key) | 根据指定的键从对象存储空间中读取数据 |
更新数据 | objectStore.put(data) | 更新对象存储空间中的数据 |
删除数据 | objectStore.delete(key) | 根据指定的键从对象存储空间中删除数据 |
下面举个例子,展示一下增删改查数据
// 打开数据库成功
request.onsuccess = function(event) {
const db = event.target.result;
// 获取事务
const transaction = db.transaction(["users"], "readwrite");
const objectStore = transaction.objectStore("users");
const userA = { name: "小A", age: 25, email: "xiaoA@example.com" };
const userB = { name: "小B", age: 27, email: "xiaoB@example.com" };
// 向用户信息表插入数据
objectStore.add(userA);
// 根据主键查询用户
const getRequest = objectStore.get(1);
getRequest.onsuccess = function(event) {
const user = event.target.result;
if (user) {
console.log("用户信息:", user);
// 更新用户的属性
user.age = 40;
// 将更新后的用户保存回对象存储空间
const updateRequest = objectStore.put(user);
} else {
console.log("找不到用户");
}
};
// 根据主键删除用户
const deleteRequest = objectStore.delete(1);
// 所有任务完成后触发oncomplete事件
transaction.oncomplete = function() {
console.log("数据插入成功!");
db.close();
};
};
发生错误或者需要回滚时,调用事务的 abort()
方法
let store = transaction.objectStore('users');
let request = store.add({ id: 1, name: '小C' });
// 发生错误或者需要回滚时,调用事务的 abort() 方法
request.onerror = function(event) {
transaction.abort();
};
索引和游标
索引
索引在 IndexedDB 中扮演着非常重要的角色,它们可以提高数据检索的效率和灵活性。索引允许你通过特定属性或字段快速查找对象。通过创建索引,可以在执行查询时避免全表扫描,从而提高查询的速度。索引会为相应的属性创建一个数据结构,使得根据该属性进行查找更加高效。
使用对象存储空间的 createIndex()
方法创建索引。该方法接受三个参数:
- 索引的名称:一个字符串,用于标识索引。
- 索引的属性或键路径:一个字符串或字符串数组,指定要索引的属性或键路径。
- 可选的配置对象:用于定义索引的配置选项,例如唯一性约束、多值索引等。
// 创建索引
objectStore.createIndex("nameIndex", "name", { unique: false });
游标(Cursor)
在 IndexedDB 中,没有像 SQL 中的 WHERE 条件语句那样直接的操作。但你可以使用游标(Cursor)来筛选和过滤数据,实现类似于 WHERE 的操作。
使用对象存储空间的 openCursor
方法打开一个游标。也可以使用索引对象的 openCursor
方法或对象存储空间的 openCursor
方法
const transaction = db.transaction(["users"], "readwrite");
const objectStore = transaction.objectStore("users");
const nameIndex = objectStore.index("nameIndex");
const request = objectStore.openCursor();
// 或者使用 const request = nameIndex.openCursor();
request.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
const key = cursor.key; // 获取键值
const data = cursor.value; // 获取数据值
// 处理数据
if(data.id ===1){
data.name = "New Name";
}
// 更新数据项
const updateRequest = cursor.update(data);
updateRequest.onsuccess = function() {
console.log("Data updated successfully.");
};
cursor.continue(); // 移动到下一个数据项
}
};
API总结
之后再总结一下常用的 IndexedDB API
方法 | 描述 |
---|---|
indexedDB.open() | 打开或创建一个 IndexedDB 数据库 |
db.createObjectStore() | 创建一个对象存储空间(表) |
db.deleteObjectStore() | 删除指定的对象存储空间(表) |
db.transaction() | 开启一个事务 |
db.onupgradeneeded | 数据库版本发生变化时的事件监听器 |
transaction.objectStore() | 获取指定的对象存储空间(表) |
store.put() | 将数据存储到对象存储空间 |
store.add() | 将数据添加到对象存储空间 |
store.get() | 通过主键获取对象存储空间中的数据 |
store.delete() | 删除对象存储空间中指定主键的数据 |
store.clear() | 清空对象存储空间中的所有数据 |
store.count() | 获取对象存储空间中的记录数量 |
store.index() | 获取指定索引的对象 |
index.get() | 通过索引键获取对象存储空间中的数据 |
index.openCursor() | 打开索引的游标,用于遍历索引的结果集 |
cursor.continue() | 移动游标到下一个位置 |
cursor.update() | 更新游标当前位置的数据 |
cursor.delete() | 删除游标当前位置的数据 |
transaction.oncomplete | 所有任务完成时的事件监听器 |
transaction.onabort | 事务中止时的事件监听器 |
transaction.abort | 事务回滚 |
需要关注的一点是IndexedDB 的 API 都是异步的。这是因为 IndexedDB 在执行许多操作时需要与浏览器进行通信,并且这些操作可能涉及到磁盘读写等耗时操作,因此使用异步操作可以避免阻塞主线程,保持程序的响应性能。
IndexedDB 提供的异步 API 使用了 Promise 对象来处理操作结果。在执行 IndexedDB 的各种操作(如打开数据库、创建对象存储空间、插入、更新、删除数据等)时,会返回一个 Promise 对象。可以使用 Promise 的 then() 方法或 async/await 语法来处理操作的结果。
转载自:https://juejin.cn/post/7250914419579093029