IndexedDB 是浏览器内置的非关系型数据库(NoSQL),适合存储大量结构化数据(如离线应用数据、用户配置、缓存等),具有异步操作、支持事务、键值对存储、索引查询等特性。以下是其核心概念及实用方法详解:
一、核心概念
- 数据库(Database):IndexedDB 的顶层容器,每个数据库有唯一名称和版本号(版本号为正整数,用于结构升级)。
- 对象存储空间(Object Store):类似关系型数据库的 “表”,用于存储数据记录(键值对)。
- 事务(Transaction):操作的最小单位,确保一组操作要么全部成功,要么全部失败,保证数据一致性。
- 索引(Index):基于对象存储空间中的字段创建,用于快速查询数据(类似数据库索引)。
- 游标(Cursor):用于遍历对象存储空间中的数据,支持条件筛选和排序。
二、实用方法与示例
1. 打开 / 创建数据库
使用 indexedDB.open() 方法,返回 IDBOpenDBRequest 对象,通过事件监听处理结果。
// 打开数据库(若不存在则创建)
const request = indexedDB.open('myDB', 1); // 数据库名、版本号
// 数据库版本升级/首次创建时触发(仅版本号提高时执行)
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 若不存在则创建对象存储空间(类似表)
if (!db.objectStoreNames.contains('users')) {
// 创建时需指定键路径(主键),autoIncrement 表示自增
const userStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
// 创建索引(加速查询,如按用户名查询)
userStore.createIndex('nameIndex', 'name', { unique: false }); // 索引名、字段名、是否唯一
}
};
// 打开成功
request.onsuccess = (event) => {
const db = event.target.result;
console.log('数据库打开成功', db);
// 后续操作(如增删改查)需通过事务
};
// 打开失败
request.onerror = (event) => {
console.error('数据库打开失败', event.target.error);
};
2. 增删改查(CRUD)操作
所有操作必须在事务中执行,事务通过 db.transaction() 创建,支持 readonly(默认)和 readwrite 模式。
(1)添加数据(add)
function addUser(db, userData) {
// 创建事务(指定操作的对象存储空间和模式)
const transaction = db.transaction(['users'], 'readwrite');
const userStore = transaction.objectStore('users');
// 添加数据
const request = userStore.add(userData);
request.onsuccess = () => {
console.log('数据添加成功,主键为:', request.result);
};
request.onerror = () => {
console.error('数据添加失败', request.error);
};
// 事务完成(成功或失败都会触发)
transaction.oncomplete = () => {
console.log('事务完成');
};
}
// 调用示例:添加用户
addUser(db, { name: '张三', age: 25, email: 'zhangsan@example.com' });
(2)查询数据
- 按主键查询(get):
function getUserById(db, id) {
const transaction = db.transaction(['users'], 'readonly');
const userStore = transaction.objectStore('users');
const request = userStore.get(id); // 按主键查询
request.onsuccess = () => {
console.log('查询结果:', request.result); // 若不存在则为 undefined
};
}
// 调用:查询 ID 为 1 的用户
getUserById(db, 1);
- 通过索引查询:
function getUsersByName(db, name) {
const transaction = db.transaction(['users'], 'readonly');
const userStore = transaction.objectStore('users');
const nameIndex = userStore.index('nameIndex'); // 获取索引
// 按索引查询(返回第一个匹配结果)
const request = nameIndex.get(name);
request.onsuccess = () => {
console.log('按姓名查询结果:', request.result);
};
}
// 调用:查询姓名为“张三”的用户
getUsersByName(db, '张三');
- 游标遍历(适合批量查询):
function getAllUsers(db) {
const transaction = db.transaction(['users'], 'readonly');
const userStore = transaction.objectStore('users');
// 打开游标(默认从头遍历所有数据)
const request = userStore.openCursor();
const users = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
users.push(cursor.value); // 收集当前数据
cursor.continue(); // 继续下一条
} else {
console.log('所有用户:', users); // 遍历结束
}
};
}
(3)更新数据(put)
put 方法:若主键存在则更新,不存在则新增(类似 “upsert”)。
function updateUser(db, updatedUser) {
const transaction = db.transaction(['users'], 'readwrite');
const userStore = transaction.objectStore('users');
const request = userStore.put(updatedUser); // 需要包含主键(id)
request.onsuccess = () => {
console.log('数据更新成功');
};
}
// 调用:更新 ID 为 1 的用户年龄
updateUser(db, { id: 1, name: '张三', age: 26, email: 'zhangsan@example.com' });
(4)删除数据(delete)
function deleteUser(db, id) {
const transaction = db.transaction(['users'], 'readwrite');
const userStore = transaction.objectStore('users');
const request = userStore.delete(id); // 按主键删除
request.onsuccess = () => {
console.log('数据删除成功');
};
}
// 调用:删除 ID 为 1 的用户
deleteUser(db, 1);
3. 关闭与删除数据库
- 关闭数据库:
db.close(); // 关闭当前数据库连接
- 删除数据库:
const deleteRequest = indexedDB.deleteDatabase('myDB');
deleteRequest.onsuccess = () => {
console.log('数据库删除成功');
};
三、实用技巧
- Promise 封装:IndexedDB 原生使用回调,可封装为 Promise 简化代码:
// 封装打开数据库为 Promise
function openDB(name, version, upgradeCallback) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onupgradeneeded = upgradeCallback;
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 使用示例
openDB('myDB', 1, (event) => { /* 升级逻辑 */ })
.then(db => { /* 操作数据库 */ })
.catch(err => console.error(err));
- 错误处理:监听事务的 onabort 事件(事务中断时触发):
transaction.onabort = (event) => {
console.error('事务中断:', event.target.error);
};
- 存储限制:不同浏览器对 IndexedDB 存储上限不同(通常与磁盘空间相关),超出限制会触发 QuotaExceededError,需合理清理过期数据。
- 离线支持:结合 Service Worker,可实现离线数据读写,提升 PWA(渐进式 Web 应用)体验。
总结
IndexedDB 适合需要在浏览器端存储大量结构化数据的场景,其核心是通过事务操作对象存储空间,配合索引和游标实现高效查询。掌握上述方法后,可轻松实现前端数据的持久化管理。