在纯前端环境中,JavaScript并不像服务端语言那样直接连MySQL或PostgreSQL,而是借助浏览器提供的存储接口。其中IndexedDB是一套底层API,允许网页在用户本地保存大量结构化数据,并支持索引、事务和异步查询,弥补了localStorage只能存字符串、容量小的不足。

一、IndexedDB的核心概念
IndexedDB是一个基于事务的NoSQL数据库,运行在浏览器中。它以“数据库—对象仓库(object store)—记录”的层级组织数据,每条记录都有一个主键,可以是系统自动生成,也可以由开发者指定。与关系型数据库不同,这里不需要建表语句,仓库内的数据结构可以灵活变化。
另一个关键是“事务”。所有读写操作都必须包裹在事务里,事务分为只读、读写和版本变更三种模式。如果某个事务正在修改仓库,其他读写事务会被阻塞,这保证了数据一致性。异步设计则意味着调用后通过事件或Promise拿到结果,不会卡住页面渲染。
1.1 数据库与版本
每个IndexedDB数据库都有名称和版本号。当版本号升高时,会触发onupgradeneeded回调,此时才能创建或删除对象仓库、建立索引。如果版本处理不当,旧页面持有低版本连接就会导致升级阻塞,因此关闭多余连接很重要。
打开数据库的代码非常简单,但回调逻辑必须写全。下面演示最基本的打开过程,注意错误处理和版本变更分开写,避免升级失败无声无息。
// 打开名为 appDB 的数据库,版本为 1
const request = indexedDB.open('appDB', 1);
request.onupgradeneeded = function(event) {
const db = event.target.result;
// 如果不存在用户仓库则创建,主键为 id
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', { keyPath: 'id' });
// 建立按姓名查询的索引
store.createIndex('by_name', 'name', { unique: false });
}
};
request.onsuccess = function(event) {
const db = event.target.result;
console.log('数据库打开成功');
};
request.onerror = function(event) {
console.error('打开失败', event.target.error);
};
二、写入与读取数据
写入数据要走读写事务。通过db.transaction(storeName, 'readwrite')拿到事务对象,再用objectStore方法定位仓库,调用add或put。add在主键冲突时报错,put则会覆盖。因为操作异步,通常包装成Promise更方便。
读取分为按主键直接取和通过索引游标遍历。直接取用get(key),索引查询用index('by_name').get('张三')。若需批量处理,游标能一条条吐出记录,适合分页或统计。下面示例展示Promise化写入与按主键读取。
function addUser(db, user) {
return new Promise((resolve, reject) => {
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
const req = store.put(user);
req.onsuccess = () => resolve();
req.onerror = () => reject(req.error);
});
}
function getUser(db, id) {
return new Promise((resolve, reject) => {
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const req = store.get(id);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
// 使用示例
request.onsuccess = async function(event) {
const db = event.target.result;
await addUser(db, { id: 1, name: '张三', age: 28 });
const u = await getUser(db, 1);
console.log(u);
};
2.1 使用游标遍历
当数据量较大,不宜一次全取。游标通过openCursor逐步前进,能在前端做过滤。以下代码打印所有年龄大于20的用户,体现了IndexedDB对非主键字段的灵活处理。
游标回调里要手动调用cursor.continue()才会取下一行,否则停止。这种拉取方式对内存友好,也方便在遍历时做增量渲染。
function eachAdult(db) {
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const req = store.openCursor();
req.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
const user = cursor.value;
if (user.age > 20) {
console.log(user.name);
}
cursor.continue();
}
};
}
三、常见误区与注意事项
不少人把IndexedDB当同步存储用,在事务外直接读变量,结果拿到undefined。必须明确:所有数据操作都在事务生命周期内有效,事务结束后对象仓库引用失效。另外,隐私模式或部分浏览器会限制容量,写入前最好捕获异常。
还有一个坑是版本升级时旧连接未关闭。若用户打开了两个标签页,一个升级数据库,另一个还占着旧版本,升级事务会一直挂起。解决办法是在onversionchange里主动调用db.close(),提示用户刷新。
IndexedDB虽强,但不是替代服务端的方案。它用于离线缓存、本地草稿、日志队列等,敏感数据仍需加密且不可信赖客户端。
3.1 简单对比localStorage
下表列出两者差异,帮助选型。若只是存 token 或配置,localStorage更简单;若存聊天记录、表单离线队列,IndexedDB才合适。
| 特性 | localStorage | IndexedDB |
|---|---|---|
| 数据类型 | 仅字符串 | 结构化对象 |
| 容量 | 约5MB | 数百MB起 |
| 查询 | 全量读后筛 | 索引与游标 |
| 异步 | 同步阻塞 | 异步非阻塞 |
四、封装建议
原生API事件回调偏底层,实际项目常用Promise或第三方轻量库包装。自己封装时,建议统一错误类型、提供单例数据库连接、在页面卸载前关闭连接。这样业务代码只调用db.get()之类方法,不碰事务细节。
总之,JavaScript操作数据库在前端语境下主要指IndexedDB。掌握打开、升级、事务、索引与游标,就能在浏览器里构建稳定的本地数据层,为离线应用打下基础。
JavaScriptIndexedDB前端数据库修改时间:2026-08-06 23:03:32