第一部分:基础概念
1.1 什么是 IndexedDB
IndexedDB(索引数据库)是浏览器提供的一种客户端结构化数据存储解决方案。它是 Web Storage(LocalStorage 和 SessionStorage)的升级版,专为存储大量结构化数据而设计。
1.1.1 IndexedDB 的定位
在现代 Web 技术栈中,IndexedDB 位于以下位置:
| 存储技术 | 容量 | 数据结构 | 适用场景 |
|---|---|---|---|
| Cookies | 4KB | 字符串键值对 | 会话状态、跟踪标识 |
| LocalStorage | 5-10MB | 字符串键值对 | 简单配置、用户偏好 |
| SessionStorage | 5-10MB | 字符串键值对 | 临时会话数据 |
| IndexedDB | 250MB+ (依浏览器而定) | 结构化对象 | 复杂数据、离线应用 |
| Cache API | 磁盘空间限制 | HTTP 请求/响应 | Service Worker 缓存 |
| Origin Private File System | 磁盘空间限制 | 文件系统 | 大文件存储 |
IndexedDB 的核心优势在于:
- 结构化存储:存储 JavaScript 对象,而非仅仅是字符串
- 索引支持:可以对对象的属性建立索引,实现高效查询
- 事务支持:所有操作都在事务中进行,保证数据完整性
- 异步 API:不会阻塞主线程,适合处理大量数据
- 大容量:相比 LocalStorage,可以存储更多数据
1.1.2 IndexedDB 的历史演进
IndexedDB 的发展历程:
- 2010年:W3C 开始制定 IndexedDB 规范,目标是替代 Web SQL Database
- 2011-2012年:Chrome、Firefox 开始实验性支持
- 2013年:IE10 引入 IndexedDB 支持
- 2015年:成为 W3C 推荐标准
- 2017年:Safari 10 加入支持,实现跨浏览器兼容
- 2018年:Promise 封装(IndexedDB 2.0)普及
- 2022年:IndexedDB API 规范化,idbjd 等库成熟
1.2 核心概念详解
1.2.1 数据库(Database)
IndexedDB 中的数据库类似于关系型数据库中的数据库概念,但更为简单:
- 每个数据库有一个名称(字符串)
- 每个数据库有一个版本号(整数,从1开始)
- 数据库中包含多个对象存储空间
- 同一域名下,不同页面可以访问同一个数据库
数据库命名规范:
- 使用有意义的名称,如 “EmailClientDB”、“GameProgressDB”
- 避免使用特殊字符和空格
- 考虑添加版本标识,如 “MyApp_v2”
版本号策略:
- 版本号必须是正整数(1, 2, 3…)
- 每次修改数据库结构(添加/删除对象存储、索引)必须增加版本号
- 版本号只能增加,不能减少
- 建议使用语义化版本控制的思想:主版本.次版本 映射到单一整数
1.2.2 对象存储空间(Object Store)
对象存储空间是 IndexedDB 的核心数据结构,类似于关系型数据库中的表:
- 存储的是 JavaScript 对象(键值对集合)
- 每个对象必须有一个主键(key)
- 主键可以是对象的一个属性,也可以是独立生成的
- 同一对象存储空间中的对象不需要有相同的结构(schema-less)
主键类型:
- 内联键(in-line key):使用对象的某个属性作为主键
// keyPath 指定主键属性 store.createObjectStore('users', { keyPath: 'userId' });
- 外联键(out-of-line key):主键与对象数据分离
// autoIncrement 让数据库自动生成递增整数作为主键 store.createObjectStore('logs', { autoIncrement: true });
- 复合主键:使用数组指定多个属性组成主键
// userId + projectId 组合成唯一主键 store.createObjectStore('memberships', { keyPath: ['userId', 'projectId'] });
1.2.3 索引(Index)
索引用于加速对非主键属性的查询:
- 可以在对象的任意属性上创建索引
- 索引可以是唯一的或非唯一的
- 复合索引可以在多个属性上建立
- 索引会增加存储空间,但能显著提升查询速度
索引的类型:
- 普通索引:允许重复值
store.createIndex('ageIndex', 'age', { unique: false });
- 唯一索引:不允许重复值
store.createIndex('emailIndex', 'email', { unique: true });
- 多入口索引(MultiEntry):用于数组属性
// 如果 tags 是数组 ['js', 'web', 'api'] // 会创建三个索引条目 store.createIndex('tagIndex', 'tags', { multiEntry: true });
1.2.4 事务(Transaction)
事务是 IndexedDB 中所有数据操作的执行单元:
- ACID 特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
- 三种模式:
- readonly:只读,可以并发执行多个
- readwrite:读写,同一对象存储空间同时只能有一个
- versionchange:版本变更,用于 onupgradeneeded 回调
事务生命周期:
// 1. 开启事务 const transaction = db.transaction(['store1', 'store2'], 'readwrite'); // 2. 获取对象存储空间引用 const store1 = transaction.objectStore('store1'); const store2 = transaction.objectStore('store2'); // 3. 执行操作 store1.add(data1); store2.put(data2); // 4. 监听完成和错误 transaction.oncomplete = () => console.log('事务完成'); transaction.onerror = (e) => console.error('事务错误', e);
1.2.5 游标(Cursor)
游标用于遍历对象存储空间或索引中的记录:
- 可以按主键或索引顺序遍历
- 支持范围查询(大于、小于、介于)
- 可以在遍历过程中修改或删除记录
- 适合处理大量数据,避免一次性加载所有数据
游标类型:
- 普通游标:遍历对象存储空间
- 索引游标:遍历索引
- 键游标:只遍历键,不加载完整对象
1.3 IndexedDB 的架构设计
1.3.1 整体架构
IndexedDB 的架构可以分为以下几个层次:
| 层次 | 职责 | 对应 API |
|---|---|---|
| 应用层 | 业务逻辑、数据模型 | 开发者自定义代码 |
| 封装层(可选) | Promise 封装、ORM 抽象 | idb、Dexie.js、localForage |
| API 层 | 原生 IndexedDB API | indexedDB.open(), IDBDatabase |
| 事务层 | 事务管理、并发控制 | IDBTransaction |
| 存储层 | 数据持久化、索引管理 | 浏览器内部实现 |
1.3.2 异步事件模型
IndexedDB 使用基于事件的异步 API:
const request = indexedDB.open('myDB', 1); // 请求对象有三种主要事件 request.onsuccess = function(event) { // 请求成功 const db = event.target.result; }; request.onerror = function(event) { // 请求失败 console.error('错误:', event.target.error); }; request.onupgradeneeded = function(event) { // 数据库需要升级(版本号增加时触发) const db = event.target.result; // 在这里创建对象存储和索引 };
1.3.3 存储配额管理
浏览器对 IndexedDB 的存储有一定限制:
- 临时存储(Temporary):可以被浏览器自动清理
- Chrome:可用磁盘空间的 60%
- Firefox:可用磁盘空间的 50%
- Safari:约 1GB
- 持久存储(Persistent):不会被浏览器自动清理
- 需要用户授权
- API:`navigator.storage.persist()`
检查存储配额:
navigator.storage.estimate().then(estimate => { console.log(`已使用: ${estimate.usage} bytes`); console.log(`配额: ${estimate.quota} bytes`); console.log(`剩余: ${estimate.quota - estimate.usage} bytes`); });
1.4 适用场景分析
1.4.1 典型应用场景
场景一:离线 Web 应用
PWA(渐进式 Web 应用)使用 IndexedDB 存储:
- 用户数据(文档、邮件、任务)
- 应用状态(表单草稿、未发送消息)
- 资源缓存(图片、音频、视频元数据)
场景二:数据密集型应用
- 数据可视化工具(存储大量数据点)
- 电子表格应用(单元格数据、公式)
- 图片编辑器(图层信息、操作历史)
场景三:游戏开发
- 游戏进度存档
- 关卡数据缓存
- 用户设置和成就
场景四:内容管理系统
- 文章草稿自动保存
- 媒体资源元数据
- 用户偏好设置
1.4.2 不适用场景
不要使用 IndexedDB 的场景:
- 简单键值对:如果只需要存储少量字符串,使用 LocalStorage
- 会话数据:需要页面关闭后清除的数据,使用 SessionStorage
- 敏感数据:不要在 IndexedDB 中存储密码、密钥等敏感信息
- 大文件存储:对于视频、大型图片,使用 OPFS 或 Cache API
- 需要 SQL 查询:IndexedDB 不支持 SQL,考虑使用 Web SQL(已废弃)或 SQLite WASM
1.5 IndexedDB 与其他存储技术对比
1.5.1 与 LocalStorage 对比
| 特性 | IndexedDB | LocalStorage |
|---|---|---|
| 存储容量 | 250MB+ | 5-10MB |
| 数据类型 | 结构化对象 | 仅字符串 |
| 同步/异步 | 异步 | 同步 |
| 索引 | 支持 | 不支持 |
| 事务 | 支持 | 不支持 |
| 性能 | 适合大量数据 | 适合少量数据 |
| 使用复杂度 | 较高 | 简单 |
选择建议:
- 数据量 > 5MB → IndexedDB
- 需要索引或查询 → IndexedDB
- 简单配置数据 → LocalStorage
1.5.2 与 Cache API 对比
Cache API 主要用于 Service Worker 缓存 HTTP 请求:
| 特性 | IndexedDB | Cache API |
|---|---|---|
| 设计目的 | 结构化数据存储 | HTTP 响应缓存 |
| 存储内容 | JS 对象 | Request/Response 对 |
| 索引 | 支持 | 不支持 |
| 查询能力 | 强大 | 简单 URL 匹配 |
| 与 SW 集成 | 可配合使用 | 原生支持 |
1.5.3 与 OPFS 对比
Origin Private File System 提供类似文件系统的 API:
| 特性 | IndexedDB | OPFS |
|---|---|---|
| 数据模型 | 记录/对象 | 文件/目录 |
| 随机访问 | 通过游标 | 通过文件句柄 |
| 流式处理 | 不支持原生 | 支持 |
| 大文件 | 不适合 | 适合 |
| 结构化查询 | 强大 | 不支持 |
1.6 安全性考虑
1.6.1 同源策略
IndexedDB 遵循同源策略:
- 协议、域名、端口必须完全相同
- 不同页面(同一域名)可以访问相同数据库
- 子域名之间无法共享数据(除非使用 document.domain)
1.6.2 隐私模式
浏览器的隐私/无痕模式对 IndexedDB 的影响:
- 数据通常只在会话期间保留
- 关闭隐私窗口后数据会被清除
- 某些浏览器可能完全禁用 IndexedDB
- 应该始终检测 IndexedDB 是否可用:
function checkIndexedDBSupport() { return new Promise((resolve) => { try { const request = indexedDB.open('__test__'); request.onsuccess = () => resolve(true); request.onerror = () => resolve(false); } catch (e) { resolve(false); } }); }
1.6.3 数据加密
虽然 IndexedDB 本身不提供加密,但你可以:
- 使用 Web Crypto API 加密敏感字段
- 只存储非敏感的引用,敏感数据存储在服务器
- 使用 SubtleCrypto 进行客户端加密
1.7 现代 IndexedDB 封装库
1.7.1 为什么需要封装
原生 IndexedDB API 的特点:
- 基于事件的异步模型,代码较为冗长
- 事务管理需要手动处理
- 错误处理分散在各处
- 类型安全较弱
1.7.2 主流封装库
idb — 轻量级 Promise 封装:
import { openDB } from 'idb'; const db = await openDB('myDB', 1, { upgrade(db) { db.createObjectStore('users', { keyPath: 'id' }); } }); await db.add('users', { id: 1, name: '张三' });
Dexie.js — 功能丰富的 IndexedDB 封装:
const db = new Dexie('myDB'); db.version(1).stores({ users: '++id, name, age' }); await db.users.add({ name: '张三', age: 25 }); const youngUsers = await db.users.where('age').below(30).toArray();
1.8 本章小结
本章介绍了 IndexedDB 的基础概念:
- IndexedDB 是浏览器提供的结构化数据存储方案
- 核心概念包括数据库、对象存储、索引、事务和游标
- 适用于需要存储大量结构化数据的离线应用场景
- 与 LocalStorage、Cache API 各有适用场景
- 遵循同源策略,在隐私模式下可能受限