JavaScript 中的 IndexedDB,是 HTML5 提供的客户端非关系型(NoSQL)数据库,相比 Cookie、Web Storage 具备更大的存储容量、更强大的数据操作能力,是前端处理大量结构化数据的核心方案。
一、IndexedDB 核心概念(先理清术语)
IndexedDB 是异步操作的本地数据库,核心术语理解起来不难:
| 术语 | 含义 |
|---|---|
| 数据库(DB) | 一个域名下可创建多个数据库,每个数据库有唯一名称 |
| 对象仓库(Store) | 类似关系型数据库的「表」,用于存储同类型数据(如 user、goods) |
| 索引(Index) | 为对象仓库的指定字段创建索引,加速数据查询(类似数据库索引) |
| 事务(Transaction) | 操作数据库的最小单元,所有读写操作必须在事务中执行,保证操作原子性 |
| 游标(Cursor) | 遍历对象仓库中数据的工具,支持分页、条件筛选 |
二、完整使用示例(封装易用的工具函数)
IndexedDB 原生 API 偏底层且异步,我会封装一个通用工具类,包含「打开数据库、增删改查」核心操作,你可以直接复用:
1. 完整封装代码
运行
class IndexedDBHelper {
constructor(dbName, version, storeConfig) {
this.dbName = dbName; // 数据库名
this.version = version; // 数据库版本(升级需递增)
this.storeConfig = storeConfig; // 对象仓库配置 {name: 'user', keyPath: 'id'}
this.db = null; // 数据库实例
}
// 1. 打开/创建数据库
openDB() {
return new Promise((resolve, reject) => {
// 关闭已存在的连接
if (this.db) this.db.close();
// 打开数据库(不存在则创建)
const request = indexedDB.open(this.dbName, this.version);
// 数据库首次创建/版本升级时触发(用于初始化对象仓库/索引)
request.onupgradeneeded = (e) => {
this.db = e.target.result;
const { name, keyPath, indexs } = this.storeConfig;
// 判断对象仓库是否存在,不存在则创建
if (!this.db.objectStoreNames.contains(name)) {
const store = this.db.createObjectStore(name, { keyPath }); // keyPath 为主键
// 可选:创建索引(如为name字段创建索引,允许重复)
if (indexs && indexs.length) {
indexs.forEach(({ field, unique }) => {
store.createIndex(field, field, { unique });
});
}
}
};
// 打开成功
request.onsuccess = (e) => {
this.db = e.target.result;
resolve(this.db);
};
// 打开失败
request.onerror = (e) => {
reject(`打开数据库失败:${e.target.error.message}`);
};
});
}
// 2. 增/改数据(主键存在则更新,不存在则新增)
putData(data) {
return new Promise((resolve, reject) => {
if (!this.db) return reject('数据库未打开');
// 创建事务(读写模式),指定操作的对象仓库
const transaction = this.db.transaction([this.storeConfig.name], 'readwrite');
const store = transaction.objectStore(this.storeConfig.name);
// 执行增/改操作
const request = store.put(data); // 单条数据
// 批量操作:store.put(data1); store.put(data2);
request.onsuccess = () => resolve('操作成功');
request.onerror = (e) => reject(`操作失败:${e.target.error.message}`);
});
}
// 3. 根据主键查询单条数据
getDataByKey(key) {
return new Promise((resolve, reject) => {
if (!this.db) return reject('数据库未打开');
const transaction = this.db.transaction([this.storeConfig.name], 'readonly');
const store = transaction.objectStore(this.storeConfig.name);
const request = store.get(key);
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(`查询失败:${e.target.error.message}`);
});
}
// 4. 查询所有数据(或通过游标筛选)
getAllData(filterFn) {
return new Promise((resolve, reject) => {
if (!this.db) return reject('数据库未打开');
const transaction = this.db.transaction([this.storeConfig.name], 'readonly');
const store = transaction.objectStore(this.storeConfig.name);
const result = [];
// 打开游标遍历所有数据
const request = store.openCursor();
request.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
// 可选:自定义筛选条件
if (!filterFn || filterFn(cursor.value)) {
result.push(cursor.value);
}
cursor.continue(); // 继续下一条
} else {
resolve(result); // 遍历完成
}
};
request.onerror = (e) => reject(`查询失败:${e.target.error.message}`);
});
}
// 5. 根据主键删除数据
deleteData(key) {
return new Promise((resolve, reject) => {
if (!this.db) return reject('数据库未打开');
const transaction = this.db.transaction([this.storeConfig.name], 'readwrite');
const store = transaction.objectStore(this.storeConfig.name);
const request = store.delete(key);
request.onsuccess = () => resolve('删除成功');
request.onerror = (e) => reject(`删除失败:${e.target.error.message}`);
});
}
// 6. 关闭数据库
closeDB() {
if (this.db) {
this.db.close();
this.db = null;
}
}
// 7. 删除数据库
deleteDB() {
return new Promise((resolve, reject) => {
this.closeDB();
const request = indexedDB.deleteDatabase(this.dbName);
request.onsuccess = () => resolve('数据库删除成功');
request.onerror = (e) => reject(`删除失败:${e.target.error.message}`);
});
}
}
2. 使用示例(增删改查)
运行
// 1. 初始化数据库(创建名为 userDB,版本1,对象仓库 user,主键 id,为 name 字段创建索引)
const dbHelper = new IndexedDBHelper('userDB', 1, {
name: 'user',
keyPath: 'id', // 主键为 id
indexs: [{ field: 'name', unique: false }] // 为 name 字段创建非唯一索引
});
// 2. 打开数据库并执行操作
async function useDB() {
try {
// 打开数据库
await dbHelper.openDB();
// 新增/修改数据
await dbHelper.putData({ id: 1, name: '张三', age: 20 });
await dbHelper.putData({ id: 2, name: '李四', age: 22 });
// 根据主键查询
const user1 = await dbHelper.getDataByKey(1);
console.log('查询id=1的用户:', user1); // 输出:{id:1, name:'张三', age:20}
// 查询所有数据(筛选年龄>20的用户)
const allUsers = await dbHelper.getAllData((user) => user.age > 20);
console.log('年龄>20的用户:', allUsers); // 输出:[{id:2, name:'李四', age:22}]
// 删除数据
await dbHelper.deleteData(2);
} catch (error) {
console.error('操作失败:', error);
} finally {
// 关闭数据库(可选,浏览器会自动管理连接)
// dbHelper.closeDB();
}
}
// 执行操作
useDB();
三、关键特性与注意事项
-
异步操作:
- IndexedDB 所有操作都是异步的(避免阻塞主线程),需通过 Promise/async-await 处理回调;
- 区别于 Web Storage 的同步操作,适合处理大量数据。
-
存储容量:
- 无固定上限(远大于 Web Storage 的 5-10MB),通常为浏览器磁盘空间的 50% 左右;
- 存储大文件(如图片、音频)时,可使用
Blob类型直接存储。
-
同源策略:
- 仅同源(协议 + 域名 + 端口)页面可访问同一数据库,保证数据隔离。
-
版本管理:
- 数据库版本号必须是整数,升级需递增版本号;
- 只有版本升级时,
onupgradeneeded才会触发,可在此修改对象仓库 / 索引结构。
-
安全性:
- 数据存储在客户端,可被用户手动清除,不建议存储敏感数据;
- 受 XSS 攻击影响(恶意脚本可读写数据),需做好输入过滤。
四、IndexedDB vs Web Storage vs Cookie(核心对比)
| 特性 | IndexedDB | Web Storage (localStorage/sessionStorage) | Cookie |
|---|---|---|---|
| 存储容量 | 超大(无固定上限) | 5-10MB | 约 4KB |
| 操作方式 | 异步(Promise / 回调) | 同步 | 同步 |
| 数据类型 | 支持任意类型(对象、Blob) | 仅字符串(需序列化) | 仅字符串 |
| 查询能力 | 支持索引、游标、复杂筛选 | 仅键值对查询(需遍历) | 仅键值对查询 |
| 网络传输 | 仅本地存储,不参与传输 | 仅本地存储,不参与传输 | 随 HTTP 请求发送 |
| 适用场景 | 大量结构化数据、离线应用 | 简单键值对存储(偏好设置、临时状态) | 少量数据(登录态) |
总结
- 核心定位:IndexedDB 是前端本地「大型数据库」,适合存储大量结构化数据、实现离线应用;
- 使用要点:操作异步且需通过事务执行,需封装 Promise 简化 API,版本升级需递增版本号;
- 选型建议:简单数据用 Web Storage,少量需传输数据用 Cookie,大量 / 复杂数据用 IndexedDB。