5.4 IndexedDB 本地数据库
IndexedDB 是浏览器原生提供的事务型结构化本地数据库,专为解决 Web Storage 容量上限低、仅支持字符串存储、查询能力弱的问题。
它支持大容量存储、索引查询、事务处理,能够存储海量结构化业务数据与二进制资源,是复杂离线应用、大数据本地缓存的核心存储方案。
5.4.1 核心概念与基础操作
一、核心特性
- 大容量存储:单域名存储上限通常在 50MB ~ 数百MB 级别,远高于 Web Storage 的 5MB,支持存储文件、二进制大对象。
- 结构化存储:以键值对形式存储 JavaScript 对象、数组、二进制数据等,无需手动序列化,支持嵌套数据结构。
- 异步非阻塞:所有操作均为异步事件驱动,基于回调执行,不会阻塞主线程,避免大数据量操作导致页面卡顿。
- 事务型保证:所有数据操作都在事务中执行,具备原子性、一致性,操作失败自动回滚,保证数据完整性。
- 索引与查询:支持创建多字段索引,通过索引实现高效条件查询;支持游标遍历与范围筛选。
- 同源隔离:严格遵循同源策略,不同站点无法访问对方的数据库,保障数据安全。
二、核心概念
IndexedDB 概念 | 类比关系型数据库 | 含义说明 |
Database(数据库) | 数据库实例 | 独立的存储单元,通过名称唯一标识,支持多版本管理 |
Object Store(对象仓库) | 数据表 | 数据存储的集合,存储键值对记录,每个仓库对应一类业务数据 |
Index(索引) | 索引 | 基于对象仓库的字段创建,用于加速条件查询,支持唯一约束 |
Transaction(事务) | 事务 | 数据操作的执行单元,所有读写都必须在事务中进行,分为只读/读写两种模式 |
Cursor(游标) | 遍历指针 | 用于逐条遍历对象仓库或索引中的数据,支持范围筛选与增量读取 |
三、基础操作语法
1. 打开/创建数据库
通过indexedDB.open(dbName, version)打开数据库,若数据库不存在则自动创建;版本号提升时触发升级事件。 数据库结构的修改(创建仓库、创建索引)只能在upgradeneeded事件中执行,这是 IndexedDB 版本管理的核心机制。
// 打开名为 UserDB 的数据库,版本号为 1 const request = indexedDB.open('UserDB', 1); let db = null; // 数据库升级时触发:首次创建、版本号提升 request.onupgradeneeded = function(e) { db = e.target.result; // 创建对象仓库(相当于表),指定主键为 id const userStore = db.createObjectStore('users', { keyPath: 'id' }); // 创建索引:按用户名查询,唯一索引 userStore.createIndex('name', 'name', { unique: true }); // 创建索引:按年龄查询,非唯一索引 userStore.createIndex('age', 'age', { unique: false }); }; // 数据库打开成功 request.onsuccess = function(e) { db = e.target.result; console.log('数据库初始化完成'); }; // 数据库打开失败 request.onerror = function(e) { console.error('数据库打开失败:', e.target.error.message); };2. 新增数据
使用readwrite模式的事务,add()方法用于纯新增,主键重复会抛出错误;put()方法为「新增或覆盖更新」,主键存在则更新、不存在则新增。
function addUser(user) { // 创建读写事务,指定操作的仓库 const transaction = db.transaction(['users'], 'readwrite'); // 获取对象仓库实例 const store = transaction.objectStore('users'); // 执行新增操作 const request = store.add(user); request.onsuccess = () => console.log('用户新增成功'); request.onerror = () => console.error('用户新增失败'); } // 调用示例 addUser({ id: 1, name: 'zhangsan', age: 25, city: 'beijing' });3. 查询数据
- 主键查询:get(key) 按主键获取单条数据
- 全量查询:getAll() 获取仓库中所有数据
- 索引查询:通过索引对象按指定字段快速查询
// 1. 按主键查询单条数据 function getUserById(id) { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const request = store.get(id); request.onsuccess = () => { console.log('查询结果:', request.result); }; } // 2. 按索引查询 function getUserByName(name) { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const nameIndex = store.index('name'); // 获取索引对象 const request = nameIndex.get(name); request.onsuccess = () => { console.log('按名称查询结果:', request.result); }; }4. 更新与删除
// 更新数据:put 方法,主键存在则覆盖更新 function updateUser(user) { const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); store.put(user); } // 按主键删除单条数据 function deleteUser(id) { const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); store.delete(id); } // 清空仓库所有数据 function clearAllUsers() { const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); store.clear(); }5. 游标遍历
适合大批量数据遍历、范围查询场景,避免一次性加载全部数据到内存,支持增量处理。
function traverseAllUsers() { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const request = store.openCursor(); // 打开游标 request.onsuccess = function(e) { const cursor = e.target.result; if (cursor) { console.log('当前记录:', cursor.value); cursor.continue(); // 移动到下一条 } else { console.log('遍历完成'); } }; }5.4.2 常用封装思路
原生 IndexedDB API 基于事件回调,写法繁琐、容易形成回调地狱,且版本管理、错误处理分散。实际项目中通常会进行分层封装,简化调用方式,统一管理底层逻辑。
一、封装核心目标
- 回调 Promise 化:将事件回调转为 Promise,支持 async/await 语法,大幅提升代码可读性与可维护性。
- 接口简化:屏蔽事务、请求对象等底层概念,对外提供简洁的增删改查业务方法。
- 统一管理:集中处理数据库打开、版本升级、错误捕获、事务生命周期管理。
二、基础 Promise 封装
最轻量化的封装,将单次操作包装为 Promise,适合简单业务场景快速接入。
// 封装打开数据库 function openDB(dbName, version, onUpgrade) { return new Promise((resolve, reject) => { const request = indexedDB.open(dbName, version); request.onupgradeneeded = e => onUpgrade(e.target.result); request.onsuccess = e => resolve(e.target.result); request.onerror = e => reject(e.target.error); }); } // 封装新增操作 function addItem(db, storeName, data) { return new Promise((resolve, reject) => { const transaction = db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.add(data); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } // 使用示例 async function demo() { const db = await openDB('DemoDB', 1, (db) => { // 升级回调中创建仓库 db.createObjectStore('items', { keyPath: 'id' }); }); await addItem(db, 'items', { id: 1, title: '测试数据' }); }三、类式封装
将数据库操作封装为独立工具类,统一管理数据库实例、配置与生命周期,是中型项目的主流方案。
class IndexedDBHelper { constructor(dbName, version, storesConfig) { this.dbName = dbName; this.version = version; this.storesConfig = storesConfig; // 仓库配置:名称、主键、索引 this.db = null; } // 初始化数据库 init() { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, this.version); request.onupgradeneeded = (e) => { const db = e.target.result; // 批量创建对象仓库与索引 this.storesConfig.forEach(config => { if (!db.objectStoreNames.contains(config.name)) { const store = db.createObjectStore(config.name, { keyPath: config.keyPath }); config.indexes?.forEach(idx => { store.createIndex(idx.name, idx.field, { unique: idx.unique || false }); }); } }); }; request.onsuccess = (e) => { this.db = e.target.result; resolve(this); }; request.onerror = (e) => reject(e.target.error); }); } // 新增数据 add(storeName, data) { return this._execute(storeName, 'readwrite', store => store.add(data)); } // 按主键查询 get(storeName, key) { return this._execute(storeName, 'readonly', store => store.get(key)); } // 通用事务执行方法,统一封装 Promise _execute(storeName, mode, handler) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], mode); const store = transaction.objectStore(storeName); const request = handler(store); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } } // 使用示例 const db = new IndexedDBHelper('AppDB', 1, [ { name: 'users', keyPath: 'id', indexes: [{ name: 'name', field: 'name', unique: true }] } ]); db.init().then(async () => { await db.add('users', { id: 1, name: 'lisi', age: 30 }); const user = await db.get('users', 1); });四、开源库方案
复杂项目推荐使用成熟的开源封装库,避免重复造轮子:
- Dexie.js:最主流的 IndexedDB 封装库,提供类似 MongoDB 的链式查询 API,完整支持事务、索引、范围查询,功能完善,生态成熟。
- localForage:API 风格与 Web Storage 高度一致,简单易用,自动降级适配 Web Storage、IndexedDB 等多种存储引擎,适合简单的键值存储场景。
五、适用场景与边界
- 适用场景:海量结构化业务数据存储、离线应用数据持久化、大文件/二进制数据本地缓存、复杂条件查询场景。
- 不适用场景:少量简单配置数据优先使用 localStorage;强实时性的高频读写场景需注意异步开销。