IndexedDB 是一个浏览器内置的、低级的 API,用于在客户端(用户的浏览器中)存储大量结构化数据。它是一个功能强大的事务型、面向对象的数据库系统。
可以理解为他是一个在你的浏览器里面内置的一个客户端浏览器,类似SQLite这种集成式客户端数据库。
IndexDB核心特点
- 存储大量数据:相比 localStorage(通常限制在 5MB 左右)和 sessionStorage,IndexedDB 可以存储显著更多的数据(具体上限由浏览器和用户磁盘空间决定,通常是磁盘空间的很大一部分)。
- 结构化数据: 数据存储在"对象仓库"(类似数据库的表)中,每个仓库可以包含任意数量的 JavaScript 对象(键值对)。这些对象的结构可以很复杂(嵌套对象、数组等)。
- 键值存储: 每个对象仓库中的对象都必须有一个唯一的"键"(key)来标识它。你可以使用各种类型作为键(数字、字符串、日期、数组等)。
- 索引查询: 这是 IndexedDB 名字的由来和强大之处!你可以在对象仓库上创建索引。索引允许你根据对象中除主键之外的属性值进行高效的查询和排序。没有索引的话,查询大量数据需要遍历整个仓库,效率极低。
- 事务性: 所有的数据库操作(读取、写入、更新、删除)都必须在事务中执行。事务确保操作的原子性:要么所有操作都成功完成,要么如果中途出错,所有操作都会被回滚,数据库保持操作前的状态。这保证了数据的一致性。
- 异步 API: IndexedDB 的所有操作都是异步的,使用请求和回调(或更现代的 Promise 封装库)来处理结果。这避免了阻塞主线程,保持页面响应。
- 同源策略: 每个源的 IndexedDB 数据库是独立的,只能由相同源(协议、域名、端口)的页面访问。
- 支持二进制数据: 可以存储 ArrayBuffer、Blob(如图片、文件)、File 对象等二进制数据。
适用场景
IndexedDB 非常适合需要在客户端存储和管理大量结构化数据,并且需要高效查询能力的 Web 应用程序。
-
离线优先应用 / PWA:
- 核心场景!允许应用在没有网络连接时完全正常运行。
- 将应用所需的数据(用户数据、产品目录、文章、配置等)完整存储在本地。
- 用户离线时进行读写操作(如填写表单、编辑文档)。
- 网络恢复后,再将本地修改的数据同步回服务器。
-
处理大量数据的应用:
- 数据可视化/分析仪表盘: 在本地存储大量数据集,支持快速的过滤、排序、聚合操作,无需频繁请求服务器。
- 富文本编辑器/笔记应用: 存储文档历史版本、大型文档内容、附件等。
- 邮件客户端: 在本地存储大量邮件、附件,实现快速的搜索、分类、离线阅读。
- 音乐/媒体库管理: 存储媒体文件元数据、播放列表、用户偏好等。
-
缓存大型或频繁访问的数据:
- 缓存从服务器获取的 API 响应结果(尤其是大型数据集),减少网络请求,提升后续访问速度。
- 缓存用户生成的内容草稿,防止意外丢失。
其实还有一种应用也可以使用IndexDB:聊天软件!聊天记录通常都保存在本地,更方便快速查看,也可以离线查看。大部分的聊天软件都是APP,所以用的都是内置的sql来做,比如SQLite。
使用IndexDB
核心API对象概览
- indexedDB - 全局入口对象
ini
// 打开/创建数据库
const request = window.indexedDB.open('myDatabase', 1);
-
IDBFactory (indexedDB 的实现接口) * .open(name, version) → 打开数据库 * .deleteDatabase(name) → 删除数据库 * .cmp(a, b) → 比较两个键的大小(返回 -1/0/1)
-
IDBOpenDBRequest (数据库打开请求对象)
- onsuccess - 数据库打开成功事件回调
- onupgradeneeded - 需要版本升级时触发事件回调
- onerror - 打开失败事件回调
-
IDBDatabase (数据库实例)
- .createObjectStore(name, options) → 创建对象仓库
- .deleteObjectStore(name) → 删除对象仓库
- .transaction(storeNames, mode) → 创建事务
- .close() → 关闭数据库连接
ini
request.onsuccess = (event) => {
const db = event.target.result;
};
- IDBObjectStore (对象仓库,类似数据库表) * .add(value, key?) → 添加数据(键需唯一) * .put(value, key?) → 更新或插入数据 * .get(key) → 通过主键获取数据 * .delete(key) → 删除数据 * .clear() → 清空仓库 * .index(name) → 获取索引对象 * .createIndex(name, keyPath, options) → 创建索引 * .deleteIndex(name) → 删除索引 * .openCursor(range?, direction?) → 打开游标 * .count(query?) → 统计记录数
ini
const store = db.createObjectStore('books', { keyPath: 'id' });
- IDBIndex (索引对象) * .get(key) → 通过索引键获取单条数据 * .getKey(key) → 获取主键 * .getAll(query?, count?) → 获取多条数据 * .openCursor(range?, direction?) → 打开游标(基于索引) * .openKeyCursor(range?, direction?) → 打开键游标
ini
const index = store.createIndex('author_idx', 'author');
- DBTransaction (事务控制器) * .objectStore(name) → 获取对象仓库 * .mode → 事务模式 (readonly/readwrite/versionchange) * .commit() → 手动提交事务 * .abort() → 中止事务 * oncomplete → 事务成功完成 * onerror → 事务失败
ini
const tx = db.transaction('books', 'readwrite');
-
IDBRequest (异步操作请求对象) * onsuccess → 操作成功 * onerror → 操作失败 * .result → 操作结果 * .error → 错误信息
-
IDBCursor (数据游标)
- .continue(key?) → 移动到下一条
- .advance(count) → 跳过指定条数
- .update(value) → 更新当前记录
- .delete() → 删除当前记录
- .key → 当前键
- .value → 当前值
- .direction → 游标方向 (next/prev/nextunique/prevunique)
ini
store.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log(cursor.value);
cursor.continue(); // 移动到下一条
}
};
- IDBKeyRange (键范围工具)
- .only(key) → 等于指定键
- .lowerBound(min, open?) → 最小值范围
- .upperBound(max, open?) → 最大值范围
- .bound(min, max, lowerOpen?, upperOpen?) → 双边界范围
go
// 创建范围:18 ≤ age < 30
const range = IDBKeyRange.bound(18, 30, false, true);
工作流程
- 打开数据库
ini
const request = indexedDB.open('library', 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建对象仓库
if (!db.objectStoreNames.contains('books')) {
const store = db.createObjectStore('books', { keyPath: 'isbn' });
store.createIndex('by_author', 'author', { unique: false });
}
};
注意 :打开数据库后创建对象仓库(数据库表)要在
onupgradeneeded
回调里进行,不能再onsuccess回调里执行。这是IndexDB的关键设计限制。数据库结构修改(创建/删除对象仓库)只能在 onupgradeneeded 事件中进行。当 onsuccess 触发时,数据库已经处于"就绪"状态,不允许修改结构。
- 写入数据
ini
const tx = db.transaction('books', 'readwrite');
const store = tx.objectStore('books');
store.add({
isbn: '978-3-16-148410-0',
title: 'JavaScript: The Good Parts',
author: 'Douglas Crockford'
});
tx.oncomplete = () => console.log('Data saved');
- 查询数据
javascript
// 通过主键查询
store.get('978-3-16-148410-0').onsuccess = (e) => {
console.log('Book:', e.target.result);
};
// 通过索引查询
const index = store.index('by_author');
index.getAll('Douglas Crockford').onsuccess = (e) => {
console.log('All books:', e.target.result);
};
- 范围查询
ini
// 查询价格在 $20-$50 之间的书籍
const priceRange = IDBKeyRange.bound(20, 50);
const priceIndex = store.index('by_price');
priceIndex.openCursor(priceRange).onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
console.log(cursor.value);
cursor.continue();
}
};
- 删除数据
ini
store.delete('978-3-16-148410-0').onsuccess = () => {
console.log('Book deleted');
};
注意事项
- 事务自动提交机制
事务会在所有请求完成且没有新请求时自动提交,显式调用 transaction.commit() 可提前提交
- 版本管理
数据库版本号必须是整数,修改结构需在 onupgradeneeded 中完成
- 游标遍历
游标必须通过 continue() 或 advance() 主动移动,否则只会访问第一条记录
- 错误处理层级
错误处理需在多个层级设置:
ini
// 数据库级
db.onerror = handleError;
// 事务级
tx.onerror = handleError;
// 请求级
request.onerror = handleError;
- 键路径类型 * 数字/字符串/Date 对象 * 数组(复合键) * 二进制数据(ArrayBuffer)
一个小Demo
javascript
'use client'
import { useEffect, useState } from "react";
export default function IndexDB() {
const [db, setDB] = useState<IDBDatabase | null>(null)
useEffect(() => {
if (!db) {
// 打开/创建数据库
const request = window.indexedDB.open('library', 2);
// 在 onupgradeneeded 中创建数据库的表结构
request.onupgradeneeded = (event) => {
const db = event.target?.result;
// 创建对象仓库 books
if (!db.objectStoreNames.contains('books')) {
const store = db.createObjectStore('books', { keyPath: 'id', autoIncrement: true });
store.createIndex('by_author', 'author', { unique: false });
}
// 创建对象仓库 authors
if (!db.objectStoreNames.contains('authors')) {
db.createObjectStore('authors', { keyPath: 'id', autoIncrement: true });
}
// 既可以继续创建你应用的其他对象仓库...
};
// 监听数据库打开成功事件
request.onsuccess = (event) => {
const db = event.target?.result;
console.log('数据库打开成功', db);
setDB(db)
};
// 监听数据库打开失败事件
request.onerror = (event) => {
console.error('数据库打开失败', event);
};
}
}, [db])
const addHandle = () => {
// 新增数据
const tx = db?.transaction('books', 'readwrite')
const store = tx?.objectStore('books')
store?.add({title: '三毛流浪记', date: '2025-08-7'})
// 链式写法
db?.transaction('books', 'readwrite').objectStore('books').add({
title: '鲁滨逊漂流记',
date: '2025-08-07',
author: 'Lonely'
})
}
const searchList = () => {
const tx = db?.transaction('books', 'readwrite')
const store = tx?.objectStore('books')
// 查询全部
store?.getAll().onsuccess = ((res) => {
console.log('all Book', res.target.result);
})
// 通过自增ID查询
// store?.get(8).onsuccess = ((res) => {
// console.log('res', res?.target?.result);
// })
}
return <div>
IndexDB<br/>
<button onClick={addHandle}>add</button><br/>
<button onClick={searchList}>search</button>
</div>;
}

点击add便能添加两条数据,点击search就可以查看添加的数据
