✨ 引言
如果你做过前端,一定踩过这些坑:
localStorage 限制太小,数据根本存不下每次刷新都要重新请求数据,浪费时间网络稍微差一点页面就白屏想查数据只能粗暴遍历,没有索引
于是,浏览器世界决定给我们一个“像数据库一样工作的本地数据库引擎”:
IndexedDB — 支持事务、索引、异步操作、容量巨大,专为浏览器本地结构化数据而生。
简单理解:
- localStorage = 小本子
- IndexedDB = 真·数据库
如果你曾经吐槽过localStorage只有5MB的容量、sessionStorage一刷新就没了、WebSQL已凉凉,那 IndexedDB 绝对值得你深入看看。
🔍 需求背景
随着 Web 应用“App 化”,浏览器端需要:
- 支持大量数据缓存(GB 级别,不再 5MB)
- 离线支持(断网还能正常用)
- 复杂结构数据本地化(对象、数组、Blob)
- 具有查询能力(按字段检索)
- 减少服务器压力和等待时间
典型场景包括:
- PWA 离线缓存
- 聊天记录本地存储
- 大型列表前端分页
- 图片、音视频素材描述缓存
- 电商购物车与离线下单
- 数据备份与恢复
一句话:浏览器从“渲染页面”演变为“承载应用”,而 IndexedDB 就是本地数据库基础设施。
⚙️ 工作原理
下面是“工作原理” 部分的全新重写内容,保持深度与专业感,同时避免重复你之前看到的表述:
⚙️ 工作原理(全新改写)
IndexedDB 的核心思想不是“键值对存储”,而是一套基于事件、事务与索引的数据管理系统。你可以把它理解成浏览器内置的一种NoSQL 数据库,具备以下几个关键运行机制:
🧱 1. 版本化数据库(Versioned DB)
IndexedDB严格依赖版本号。
当你第一次打开数据库,或要新增字段结构(如新增索引、调整主键)时,浏览器会要求传入更高的版本号,以触发一次:
onupgradeneeded这相当于DDL 阶段,你只能在这里创建表(Object Store)、修改结构。它避免了“随便打开就改 schema”的混乱模式。
📂 2. Object Store —— 类似表,但无固定 Schema
IndexedDB 不存“行列”,而存“对象”。
每个对象存储(Object Store)都有自己的 key 生成方式:
- 指定
keyPath - 或自动递增
autoIncrement
无需定义字段类型,也不用 schema 约束,这就让 IndexedDB 更像 MongoDB。
🔍 3. Index —— 决定查询能力的核心
IndexedDB 不允许直接“按任意字段搜索”;
如果你希望某字段能高效查询,必须提前建立索引(Index)。
存储层会维护一棵 B+ 树索引结构,保证:
- 查找复杂度为 O(log n)
- 不需要自己在数组中暴力
filter
如果未创建索引,那么你只能用 Cursor 做全量遍历。
🔐 4. Transaction —— 所有操作都必须在事务中完成
IndexedDB 禁止“裸写”。
每一次增删改查都必须通过事务执行,它的生命周期分为:
readonly— 查询场景readwrite— 数据变更versionchange— 数据库结构升级
事务一旦结束,所有 ObjectStore Handler 会失效,确保数据库不会被悬空操作污染。
⚡ 5. 全异步模型,基于事件驱动
IndexedDB 采用非阻塞 IO,核心 API 不返回 Promise,而是事件回调:
onsuccessonerroronblocked
之所以刻意不用同步,是为了避免 UI 主线程被锁死——浏览器从设计层面杜绝“卡住页面”。
也正因为这种事件体系,才催生出idb、Dexie等 Promise 封装库。
🔄 6. 数据存储格式:结构化克隆算法
IndexedDB 底层使用结构化克隆算法(Structured Clone)储存对象:
- 支持嵌套对象
- 支持 Date、Map、Blob、File
- 不需要你手动 JSON encode/decode
相比localStorage,它真正意义上能“存对象”。
🧭 7. 游标(Cursor)是读取大数据的关键
IndexedDB 在数据读取方面更像数据库游标扫描:
openCursor()遍历记录- 支持范围过滤(IDBKeyRange)
- 可用于分页、增量渲染、批量索引
游标模式天生支持“百万数据分批读取”,这也是它适用于大数据前端场景的原因。
🧩 代码实现示例
下面提供一份完整示例:演示创建数据库、插入数据、读取索引数据。
IndexedDB 是运行在浏览器内部的本地数据库,由浏览器底层实现,数据存放在用户本地磁盘中,不会上传服务器。因此它属于站点级资源(同源策略限制),只能被 同域代码访问。
1. 创建数据库与表结构
constDB_NAME='user-db'// 数据库名称constSTORE_NAME='users'// 表的名称constVERSION=1// 数据库版本号/** * 初始化 / 打开 IndexedDB 数据库 * 说明: * - indexedDB.open() 是异步调用 * - 第一次打开会创建数据库 * - 版本号升级会触发 onupgradeneeded 事件,用于修改结构 */functioninitDB(){returnnewPromise((resolve,reject)=>{// 创建或打开一个本地数据库constrequest=indexedDB.open(DB_NAME,VERSION)// 打开失败,比如权限异常或被用户禁止request.onerror=()=>reject(request.error)/** * 数据库不存在 或 版本号变更时触发 * 只允许在此事件内执行:创建表、索引、修改结构等操作 */request.onupgradeneeded=(event)=>{constdb=request.resultconststore=db.createObjectStore(STORE_NAME,{keyPath:'id'})store.createIndex('ageIndex','age',{unique:false})}request.onsuccess=()=>resolve(request.result)})}2. 插入数据
asyncfunctionaddUser(user){constdb=awaitinitDB()// 等待数据库初始化完成,获取 db 实例consttx=db.transaction(STORE_NAME,'readwrite')// 开启一个事务,类型为 'readwrite',允许读写操作conststore=tx.objectStore(STORE_NAME)store.add(user)// 将用户对象写入 store 中returntx.complete}// 示例调用addUser({id:'001',name:'Tom',age:23})3. 按索引查询
asyncfunctionqueryByAge(age){constdb=awaitinitDB()// 等待数据库初始化完成,获取 db 实例consttx=db.transaction(STORE_NAME,'readonly')// 开启一个只读事务,不允许修改数据conststore=tx.objectStore(STORE_NAME)constindex=store.index('ageIndex')// 获取之前创建的 ageIndex 索引,用于按 age 查询constrequest=index.getAll(age)// 使用索引查询所有匹配指定 age 的记录// 返回一个 Promise,封装 onsuccess 回调returnnewPromise((resolve)=>{request.onsuccess=()=>resolve(request.result)})}// 示例调用queryByAge(23).then(console.log)4. 更新数据
asyncfunctionupdateUser(id,data){constdb=awaitinitDB()consttx=db.transaction(STORE_NAME,'readwrite')conststore=tx.objectStore(STORE_NAME)constoldData=awaitstore.get(id)store.put({...oldData,...data})}5. 删除数据
asyncfunctiondeleteUser(id){constdb=awaitinitDB()consttx=db.transaction(STORE_NAME,'readwrite')conststore=tx.objectStore(STORE_NAME)store.delete(id)}🎯 解决痛点
IndexedDB 解决了很多本地存储“卡脖子”的问题:
✔ 容量大:可达 GB 级
localStorage5MB 左右,IndexedDB 没有固定上限,由浏览器与磁盘限制决定。
✔ 支持结构化对象
无需JSON.stringify(),支持复杂对象、Blob、ArrayBuffer 等。
✔ 支持索引查询
如:
查询年龄 = 23 的所有用户
无需遍历数组,调用index.getAll()即可。
✔ 异步性能高
I/O 不会阻塞 UI。
✔ 适合离线应用与 PWA
搭配ServiceWorker简直完美。
🥊 竞品分析
| 技术 | 容量 | 查询能力 | 异步 | 适用场景 |
|---|---|---|---|---|
| localStorage | ~5MB | ❌ 无索引 | ❌ 同步阻塞 | 小数据配置 |
| sessionStorage | ~5MB | ❌ 无索引 | ❌ 同步阻塞 | 会话状态 |
| Cookie | 4KB | ❌ 无索引 | ❌ 请求头传输 | 登录态 |
| WebSQL | 废弃 | ✔ SQL | ✔ 异步 | 历史包袱 |
| IndexedDB | GB 级 | ✔ 索引查询 | ✔ 异步 | 本地数据库/PWA |
一句话总结:IndexedDB 是浏览器端唯一能当“真正数据库”用的方案。
🏁 总结
IndexedDB 并不难,但坑点主要来自:
- 异步回调层层嵌套
- API 事件风格“复古”
- 查询逻辑需要理解索引和游标
但一旦掌握,IndexedDB 是前端提升性能、支持离线能力的“终极武器”。
建议:
- 提供封装方法(如上代码)
- 或使用库如
idb,Dexie.js
作者: 王新焱
博客: https://blog.csdn.net/qq_34402069
时间: 2025年12月18日