====== 第一部分:基础概念 ======
===== 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 各有适用场景
* 遵循同源策略,在隐私模式下可能受限