IndexedDB 是 HTML5 引入的一种高性能、事务型的客户端存储 API,用于在浏览器中存储大量结构化数据。它提供了比 localStorage 更强大的存储能力和更丰富的查询接口。
一、IndexedDB 简介
1.1 什么是 IndexedDB?
IndexedDB 是一种 NoSQL 数据库,允许在浏览器中存储大量结构化数据(如对象、数组等),并提供高性能的索引和查询功能。
1.2 主要特性
异步操作:所有操作都是异步的,避免阻塞主线程。
事务支持:确保数据操作的原子性、一致性、隔离性和持久性。
索引支持:可以为数据创建索引,实现高效查询。
大容量存储:存储空间通常远大于 localStorage(一般为几百 MB 甚至 GB 级别)。
结构化数据:支持存储复杂的 JavaScript 对象。
同源策略:遵循同源策略,确保数据安全。
二、IndexedDB 架构与核心概念
2.1 数据模型
IndexedDB 采用对象存储模型,与关系型数据库有以下对应关系:
关系型数据库 | IndexedDB概念 |
|---|---|
数据库(Database) | 数据库(Database) |
表(Table) | 对象存储空间(Object Store) |
表字段(Column) | 对象属性(Object Property) |
表记录(Row) | 对象(Object) |
主键(Primary Key) | 键路径(Key Path) |
索引(Index) | 索引(Index) |
IndexedDB的索引与关系型数据库表字段的概念对比:
特性 | 关系型数据库(如MySQL) | IndexedDB |
|---|---|---|
表字段 | 表的结构组成部分,定义数据的类型和属性(id、name、email),存储实际数据 | 对象存储空间中对象的属性({id: 1, name: "august", email: "august@test.com"}中的id、name、email)相当于表字段,存储实际数据. |
索引 | 基于表字段创建的优化结构,不存储实际数据,仅存储指向数据的引用,用于加速查询 | 基于对象属性创建的优化结构,不存储实际数据,仅存储属性值和指向对应对象的引用,用于加速查询 |
IndexedDB的索引与关系型数据库表字段的核心区别:
区别点 | 关系型数据库的表字段 | IndexedDB的索引 |
|---|---|---|
作用 | 定义数据结构,存储实际数据内容 | 加速基于特定属性的查询操作,提高检索效率 |
必要性 | 表必须有字段,否则无法存储数据 | 索引是可选的,对象存储空间可以没有索引(但查询效率会降低) |
存储内容 | 存储实际数据值(如字符串、数字等) | 存储属性值和指向对应对象的引用(指针) |
唯一性约束 | 可通过UNIQUE约束限制字段值的唯一性 | 可通过{ unique: true }配置限制索引属性值的唯一性 |
2.2 IndexedDB 核心概念
2.2.1 数据库 (Database)
IndexedDB 中的最高层级存储结构。
每个数据库有一个名称和版本号。
版本号必须是整数,升级数据库时版本号必须递增。
2.2.2 对象存储空间 (Object Store)
数据库中的表结构,用于存储数据。
每个对象存储空间必须有一个主键 (key)。
主键可以是自增的,也可以由开发者指定。
2.2.3 索引 (Index)
基于对象存储空间中的某个属性创建的索引。
用于加速数据查询。
可以创建单键索引或复合索引。
支持唯一索引和非唯一索引。
2.2.4 事务 (Transaction)
用于确保数据操作的原子性。
所有数据操作必须在事务中执行。
事务有三种模式:只读 (readonly)、读写 (readwrite) 和版本变更 (versionchange)。
2.2.5 游标 (Cursor)
用于遍历对象存储空间或索引中的数据。
支持正向和反向遍历。
可以设置范围和限制条件。
2.3 对象存储的两种键模式
内联键 (Inline Key)
// 使用对象的某个属性作为主键
const objectStore = db.createObjectStore('users', { keyPath: 'id' });外键 (Out-of-line Key)
// 不使用对象属性作为主键,存储时单独指定
const objectStore = db.createObjectStore('users', { autoIncrement: true });
// 存储时指定键
objectStore.add(userData, 123);2.4 索引类型
单属性索引
objectStore.createIndex('email', 'email', { unique: true });复合索引
// 复合索引只能在现代浏览器中使用
objectStore.createIndex('name_age', ['name', 'age'], { unique: false });三、IndexedDB 基本操作
3.1 打开/创建数据库的一般流程
3.1.1 数据库打开/创建请求
const request = indexedDB.open('myDatabase', 1);函数:indexedDB.open();
作用:打开或创建一个名为"myDatabase"的IndexedDB数据库。
参数:
第一个参数:数据库名称 "myDatabase"。
第二个参数:数据库版本号 1。
返回值:一个IDBOpenDBRequest对象,用于处理异步操作的结果。
3.1.2 数据库升级事件处理
request.onupgradeneeded = function(event) {
const db = event.target.result;
//创建对象存储空间
const objectStore = db.createObjectStore('users', { keyPath: 'id' });
//创建索引
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });
};事件:onupgradeneeded。
触发条件:当数据库首次创建或版本号高于现有版本时触发。
功能:
通过 event.target.result 获取数据库实例。
创建名为"users"的对象存储空间(类似于数据库表)。
参数:名称"users",配置项{ keyPath: 'id' }表示使用对象的id属性作为主键。
为对象存储空间创建索引:
name索引:非唯一,用于快速查询name字段。
email索引:唯一,确保email字段的值不重复。
3.1.3 数据库打开成功事件处理
request.onsuccess = function(event) {
const db = event.target.result;
console.log('数据库打开成功');
//在这里可以进行数据操作
};事件:onsuccess
触发条件:数据库成功打开时触发
功能:
通过 event.target.result 获取数据库实例。
可以在这个回调函数中进行后续的数据操作(如增删改查)。
3.1.4 数据库打开失败事件处理
request.onerror = function(event) {
console.error('数据库打开失败:', event.target.error);
};事件:onerror
触发条件:数据库打开失败时触发。
功能:
通过 event.target.error 获取具体的错误对象。
上述分解操作的完整示例代码:
// 打开或创建名为 "myDatabase" 的数据库,版本号为 1
const request = indexedDB.open('myDatabase', 1);
// 数据库升级或首次创建时触发
request.onupgradeneeded = function(event) {
const db = event.target.result;
// 创建对象存储空间
const objectStore = db.createObjectStore('users', { keyPath: 'id' });
// 创建索引
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });
};
// 数据库打开成功时触发
request.onsuccess = function(event) {
const db = event.target.result;
console.log('数据库打开成功');
// 在这里可以进行数据操作
};
// 数据库打开失败时触发
request.onerror = function(event) {
console.error('数据库打开失败:', event.target.error);
};3.2 添加数据
// 向IndexedDB数据库的users对象存储空间添加用户数据
// db: 已经成功打开的IndexedDB数据库实例
// user: 要添加的用户对象,必须包含id字段(作为主键)
function addUser(db, user) {
// 创建读写事务
// 第一个参数:数组,指定要操作的对象存储空间(表),这里是['users']
// 第二个参数:事务模式,'readwrite'表示可读可写(查询时可用'readonly')
const transaction = db.transaction(['users'], 'readwrite');
// transaction.objectStore():通过事务获取指定的对象存储空间实例
const objectStore = transaction.objectStore('users');
// 添加数据
// 参数:要添加的用户对象
// 返回值:请求对象(IDBRequest),用于处理异步操作结果
// 特性:如果主键(id)已存在,会抛出错误(这是与put()方法的主要区别)
const request = objectStore.add(user);
// request.onsuccess:添加成功时触发的回调函数
request.onsuccess = function() {
console.log('用户添加成功');
};
// request.onerror:添加失败时触发的回调函数
request.onerror = function() {
// 通过request.error获取具体错误信息
console.error('用户添加失败:', request.error);
};
// transaction.oncomplete:当整个事务中的所有操作都完成时触发
// 注意:这是事务级别的回调,不是单个请求的回调
transaction.oncomplete = function() {
console.log('事务完成');
};
}
// 使用示例
const user = { id: 1, name: 'august', email: 'august@example.com' };
addUser(db, user);3.3 读取数据
根据主键读取
// 根据主键读取
function getUserById(db, id) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const request = objectStore.get(id);
request.onsuccess = function() {
if (request.result) {
console.log('找到用户:', request.result);
} else {
console.log('未找到用户');
}
};
}使用索引读取
// 使用索引读取
function getUserByEmail(db, email) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const index = objectStore.index('email');
const request = index.get(email);
request.onsuccess = function() {
if (request.result) {
console.log('找到用户:', request.result);
} else {
console.log('未找到用户');
}
};
}3.4 更新数据
function updateUser(db, user) {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
const request = objectStore.put(user);
request.onsuccess = function() {
console.log('用户更新成功');
};
request.onerror = function() {
console.error('用户更新失败:', request.error);
};
}
// 使用示例
const updatedUser = { id: 1, name: '八月', email: 'bayue@new.com' };
updateUser(db, updatedUser);3.5 删除数据
function deleteUser(db, id) {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
const request = objectStore.delete(id);
request.onsuccess = function() {
console.log('用户删除成功');
};
request.onerror = function() {
console.error('用户删除失败:', request.error);
};
}
// 使用示例
deleteUser(db, 1);3.6 使用游标遍历数据
function getAllUsers(db) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const users = [];
// 打开游标
const request = objectStore.openCursor();
request.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
users.push(cursor.value);
cursor.continue(); // 移动到下一条记录
} else {
console.log('所有用户:', users);
}
};
}// 使用索引和游标查询
function getUsersByName(db, name) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const index = objectStore.index('name');
const users = [];
// 打开索引游标,只查找name为指定值的记录
const request = index.openCursor(IDBKeyRange.only(name));
request.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
users.push(cursor.value);
cursor.continue();
} else {
console.log('找到用户:', users);
}
};
}四、高级特性
4.1 事务管理
// 事务错误处理
const transaction = db.transaction(['users'], 'readwrite');
transaction.oncomplete = function() {
console.log('事务完成');
};
transaction.onerror = function() {
console.error('事务失败:', transaction.error);
};
transaction.onabort = function() {
console.log('事务被中止');
};
// 手动中止事务
// transaction.abort();4.2索引范围查找
function getUsersByAgeRange(db, minAge, maxAge) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
// objectStore.index('age'):获取users对象存储空间中名为age的索引
// 前提:数据库初始化时已为age属性创建了索引(如objectStore.createIndex('age', 'age', { unique: false }))
const index = objectStore.index('age');
const users = [];
// 创建一个范围对象,表示年龄在minAge到maxAge之间(包含边界值)
// 其他范围方法:only()(精确匹配)、lowerBound()(大于等于)、upperBound()(小于等于)
const range = IDBKeyRange.bound(minAge, maxAge);
// index.openCursor(range):使用索引打开一个游标,仅遍历年龄在指定范围内的记录
// 索引游标的优势:直接基于索引值排序,无需全表扫描,查询效率更高
const request = index.openCursor(range);
request.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
users.push(cursor.value);
cursor.continue();
} else {
resolve(users); // 成功时返回结果
}
};
request.onerror = function(event) {
reject(event.target.error); // 失败时返回错误
};
});
}
// 使用示例
getUsersByAgeRange(db, 20, 30)
.then(users => console.log('找到用户:', users))
.catch(error => console.error('查询失败:', error));4.3 数据库版本升级
// 打开或创建数据库(版本1)
const request = indexedDB.open('myDatabase', 2); // 直接请求版本2,触发升级
// 数据库升级或首次创建时触发
request.onupgradeneeded = function(event) {
const db = event.target.result;
const transaction = event.target.transaction; // 使用事件自带的事务
console.log(`数据库升级: 从版本 ${event.oldVersion} 到 ${event.newVersion}`);
// 版本1:创建初始数据库结构
if (event.oldVersion < 1) {
// 创建users对象存储空间,使用id作为主键
const userStore = db.createObjectStore('users', { keyPath: 'id' });
// 为name和email字段创建索引
userStore.createIndex('name', 'name', { unique: false });
userStore.createIndex('email', 'email', { unique: true });
console.log('版本1:创建了users对象存储空间和基本索引');
}
// 版本2:添加age索引
if (event.oldVersion < 2) {
// 获取users对象存储空间
const userStore = transaction.objectStore('users');
// 为age字段创建索引
userStore.createIndex('age', 'age', { unique: false });
console.log('版本2:为users对象存储空间添加了age索引');
}
// 版本3:如果未来需要更多升级,可以继续添加
// if (event.oldVersion < 3) {
// // 新的升级逻辑
// }
};
// 数据库打开成功时触发
request.onsuccess = function(event) {
const db = event.target.result;
console.log('数据库打开成功,当前版本:', db.version);
// 数据库操作示例:添加一个带age字段的用户
addUserWithAge(db, { id: 1, name: 'September', email: 'september@example.com', age: 30 });
// 关闭数据库(实际应用中可根据需要决定何时关闭)
// db.close();
};
// 数据库打开失败时触发
request.onerror = function(event) {
console.error('数据库操作失败:', event.target.error);
};
// 添加带age字段的用户示例
function addUserWithAge(db, user) {
const transaction = db.transaction(['users'], 'readwrite');
const userStore = transaction.objectStore('users');
const addRequest = userStore.put(user); // 使用put,支持添加和更新
addRequest.onsuccess = function() {
console.log('用户添加/更新成功:', user);
};
addRequest.onerror = function() {
console.error('用户添加/更新失败:', addRequest.error);
};
}五、高级查询技术
5.1 键范围 (IDBKeyRange)
精确匹配
// 精确匹配
const exactRange = IDBKeyRange.only('value');大于等于
// 大于等于
const lowerBoundRange = IDBKeyRange.lowerBound(10);大于
// 大于
const lowerBoundOpenRange = IDBKeyRange.lowerBound(10, true);小于等于
// 小于等于
const upperBoundRange = IDBKeyRange.upperBound(100);小于
// 小于
const upperBoundOpenRange = IDBKeyRange.upperBound(100, true);范围匹配(闭区间)
// 范围匹配(闭区间)
const boundRange = IDBKeyRange.bound(10, 100);范围匹配(开区间)
// 范围匹配(开区间)
const boundOpenRange = IDBKeyRange.bound(10, 100, true, true);5.2 高级游标操作
5.2.1 方向控制
正向遍历(默认)
// 正向遍历(默认)
objectStore.openCursor(range, 'next');正向遍历,跳过重复键
// 正向遍历,跳过重复键
objectStore.openCursor(range, 'nextunique');反向遍历
// 反向遍历
objectStore.openCursor(range, 'prev');反向遍历,跳过重复键
// 反向遍历,跳过重复键
objectStore.openCursor(range, 'prevunique');5.2.2 游标分页
function getUsersPage(db, pageSize, pageNumber) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const users = [];
const skipCount = pageSize * (pageNumber - 1);
let count = 0;
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (count >= skipCount && count < skipCount + pageSize) {
users.push(cursor.value);
}
count++;
if (count < skipCount + pageSize) {
cursor.continue();
} else {
resolve(users);
}
} else {
resolve(users);
}
};
request.onerror = () => reject(request.error);
});
}5.2.3 索引迭代与性能优化
// 使用索引高效查询
function findUserByEmail(db, email) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['users'], 'readonly');
const index = transaction.objectStore('users').index('email');
// 使用get()直接获取,比游标更高效
const request = index.get(email);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}六、事务管理与并发控制
6.1 事务生命周期
const transaction = db.transaction(['users'], 'readwrite');
// 事务创建时的状态:active
// 执行数据操作
const objectStore = transaction.objectStore('users');
objectStore.add({ id: 1, name: 'September' });
// 事务完成时
transaction.oncomplete = () => console.log('事务已提交');
// 事务失败时
transaction.onerror = () => console.log('事务已回滚');
// 事务被中止时
transaction.onabort = () => console.log('事务已中止');
// 手动中止事务
// transaction.abort();6.2 并发控制
IndexedDB 使用乐观并发控制,当两个事务同时修改同一数据时:
第一个提交的事务会成功。
第二个提交的事务会失败并触发 VersionError。
// 处理并发冲突
async function updateUserSafely(db, userId, updateFn) {
while (true) {
try {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
// 读取当前数据
const user = await new Promise((resolve, reject) => {
const request = objectStore.get(userId);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
// 应用更新
const updatedUser = updateFn(user);
// 保存更新
await new Promise((resolve, reject) => {
const request = objectStore.put(updatedUser);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
return updatedUser;
} catch (error) {
if (error.name === 'VersionError') {
// 并发冲突,重试
continue;
}
throw error;
}
}
}七、高级数据库设计
7.1 数据库版本管理
function openDatabase(name, version, upgradeCallback) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
if (upgradeCallback) {
upgradeCallback(db, oldVersion, version);
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 使用示例
openDatabase('myDB', 2, (db, oldVersion, newVersion) => {
if (oldVersion < 1) {
// 版本1:创建users对象存储
const usersStore = db.createObjectStore('users', { keyPath: 'id' });
usersStore.createIndex('email', 'email', { unique: true });
}
if (oldVersion < 2) {
// 版本2:创建posts对象存储
const postsStore = db.createObjectStore('posts', { keyPath: 'id' });
postsStore.createIndex('userId', 'userId', { unique: false });
postsStore.createIndex('createdAt', 'createdAt', { unique: false });
}
});7.2 关系数据建模
// 一对多关系示例:用户-文章
function setupRelationalDatabase(db) {
// 创建用户存储
const usersStore = db.createObjectStore('users', { keyPath: 'id' });
// 创建文章存储
const postsStore = db.createObjectStore('posts', { keyPath: 'id' });
// 创建文章的用户ID索引,用于查询用户的所有文章
postsStore.createIndex('userId', 'userId', { unique: false });
// 创建文章的发布时间索引
postsStore.createIndex('publishedAt', 'publishedAt', { unique: false });
}
// 查询用户的所有文章
function getUserPosts(db, userId) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['posts'], 'readonly');
const index = transaction.objectStore('posts').index('userId');
const posts = [];
const request = index.openCursor(IDBKeyRange.only(userId));
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
posts.push(cursor.value);
cursor.continue();
} else {
resolve(posts);
}
};
request.onerror = () => reject(request.error);
});
}八、性能优化与最佳实践
8.1 批量操作优化
// 批量添加数据
function bulkAddUsers(db, users) {
return new Promise((resolve, reject) => {
// 使用单个事务处理所有添加操作
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
// 为每个用户创建添加请求
users.forEach(user => {
objectStore.add(user);
});
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
});
}8.2 索引设计原则
只为频繁查询的属性创建索引。
唯一索引比非唯一索引查询更快。
避免创建过多索引(会影响写入性能)。
复合索引应将最常用的查询条件放在前面
8.3 内存管理
// 处理大量数据时避免内存溢出
function processLargeDataset(db) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['largeData'], 'readonly');
const objectStore = transaction.objectStore('largeData');
let processedCount = 0;
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
// 处理当前记录
processRecord(cursor.value);
processedCount++;
// 释放当前记录的引用
cursor.value = null;
// 继续处理下一条记录
cursor.continue();
} else {
resolve(processedCount);
}
};
request.onerror = () => reject(request.error);
});
}九、实际应用案例
9.1 离线数据同步
class OfflineSyncManager {
constructor(dbName) {
this.dbName = dbName;
this.db = null;
}
async init() {
this.db = await this.openDatabase();
}
async openDatabase() {
// 打开数据库并创建必要的存储
// ...
}
async saveOfflineData(operation, data) {
// 保存离线操作到数据库
const transaction = this.db.transaction(['offlineOperations'], 'readwrite');
const store = transaction.objectStore('offlineOperations');
const operationRecord = {
id: Date.now() + Math.random(),
operation,
data,
timestamp: Date.now(),
status: 'pending'
};
await new Promise((resolve, reject) => {
const request = store.add(operationRecord);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async syncData() {
// 同步所有待处理的离线操作
const pendingOperations = await this.getPendingOperations();
for (const operation of pendingOperations) {
try {
// 尝试同步到服务器
await this.syncToServer(operation);
// 同步成功,更新状态
await this.updateOperationStatus(operation.id, 'completed');
} catch (error) {
// 同步失败,更新状态并重试
await this.updateOperationStatus(operation.id, 'failed');
}
}
}
// 其他方法...
}9.2 全文搜索实现
// 创建全文搜索索引
function createFullTextIndex(objectStore, fieldNames) {
// 为每个字段创建索引
fieldNames.forEach(fieldName => {
objectStore.createIndex(`fulltext_${fieldName}`, fieldName, { unique: false });
});
}
// 简单的全文搜索实现
async function fullTextSearch(db, storeName, query) {
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const results = new Set();
// 搜索所有全文索引字段
const indexNames = Array.from(store.indexNames).filter(name =>
name.startsWith('fulltext_')
);
for (const indexName of indexNames) {
const index = store.index(indexName);
const fieldName = indexName.replace('fulltext_', '');
// 使用游标范围查询匹配的记录
const range = IDBKeyRange.bound(query, query + '\uffff');
const cursorRequest = index.openCursor(range);
await new Promise((resolve) => {
cursorRequest.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
results.add(cursor.primaryKey);
cursor.continue();
} else {
resolve();
}
};
});
}
// 获取完整的记录数据
const finalResults = [];
for (const key of results) {
const record = await new Promise((resolve, reject) => {
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
finalResults.push(record);
}
return finalResults;
}十、常见问题与解决方案
10.1 浏览器兼容性
// 检测 IndexedDB 支持
function checkIndexedDBSupport() {
if (!('indexedDB' in window)) {
console.error('此浏览器不支持 IndexedDB');
return false;
}
// 检测特定功能支持
try {
// 检测复合索引支持
const db = indexedDB.open('testCompositeIndex', 1);
db.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('test', { keyPath: 'id' });
store.createIndex('composite', ['a', 'b'], { unique: false });
db.close();
indexedDB.deleteDatabase('testCompositeIndex');
console.log('支持复合索引');
};
} catch (error) {
console.log('不支持复合索引');
}
return true;
}10.2 安全问题
不要存储敏感信息:IndexedDB 数据可被用户访问和修改。
使用加密:对敏感数据进行加密后再存储。
验证数据:从 IndexedDB 读取数据后进行验证。
// 简单的数据加密存储
function encryptData(data, key) {
// 使用 Web Crypto API 进行加密
// ...
}
function decryptData(encryptedData, key) {
// 使用 Web Crypto API 进行解密
// ...
}
async function secureStore(db, storeName, data, encryptionKey) {
const encryptedData = await encryptData(data, encryptionKey);
return await this.storeData(db, storeName, encryptedData);
}
async function secureRetrieve(db, storeName, key, encryptionKey) {
const encryptedData = await this.retrieveData(db, storeName, key);
return await decryptData(encryptedData, encryptionKey);
}十一、IndexedDB 与其他存储方案的比较
11.1 与 Web Storage 的比较
特性 | IndexedDB | localStorage | sessionStorage |
|---|---|---|---|
存储容量 | 几百 MB 到 GB | 约 5MB | 约 5MB |
数据类型 | 复杂结构化数据 | 仅字符串 | 仅字符串 |
操作方式 | 异步 | 同步 | 同步 |
事务支持 | 支持 | 不支持 | 不支持 |
索引查询 | 支持 | 不支持 | 不支持 |
适用场景 | 大量结构化数据 | 简单键值对 | 会话数据 |
11.2 与 Cache API 的比较
特性 | IndexedDB | Cache API |
|---|---|---|
用途 | 结构化数据存储 | HTTP 响应缓存 |
API 复杂度 | 高 | 低 |
查询能力 | 强大 | 有限(基于请求 URL) |
数据类型 | 任意 JavaScript 对象 | Response 对象 |
适用场 | 应用数据存储 | 离线资源缓存 |
总结
IndexedDB 是浏览器中功能最强大的客户端存储 API,它提供了异步操作、事务支持、索引查询等高级特性,适用于需要存储大量结构化数据的 Web 应用。通过合理的数据库设计、索引优化和事务管理,可以充分发挥 IndexedDB 的性能优势,为用户提供流畅的离线体验和高性能的数据处理能力。