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