logo

IndexedDB实战指南:从入门到进阶的完整教学

作者:Nicky2025.10.13 16:18浏览量:11

简介:本文通过系统化的教学,详细解析IndexedDB的核心概念、API操作及最佳实践,帮助开发者掌握浏览器端结构化数据存储技术,提升Web应用离线能力与性能表现。

一、IndexedDB基础认知

1.1 浏览器存储技术对比

当前浏览器端主流存储方案包括:

  • Cookie:4KB容量限制,每次HTTP请求携带,适合会话管理
  • LocalStorage/SessionStorage:5MB容量,同步API,适合简单键值对存储
  • Web SQL:已废弃的SQL数据库方案
  • IndexedDB:支持GB级结构化数据存储,异步API,支持事务和索引

IndexedDB的核心优势在于其非关系型数据库特性,支持存储复杂对象、建立索引加速查询,特别适合需要处理大量结构化数据的Web应用(如邮件客户端、离线编辑器等)。

1.2 核心概念解析

  • 数据库(Database):最高层级容器,通过名称标识
  • 对象仓库(Object Store):类似关系型数据库的表,存储实际数据
  • 索引(Index):加速对象仓库的查询性能
  • 事务(Transaction):确保数据操作的原子性和一致性
  • 游标(Cursor):用于遍历查询结果集

二、基础操作实现

2.1 数据库创建与版本管理

  1. // 打开或创建数据库
  2. const request = indexedDB.open('MyDatabase', 1);
  3. request.onupgradeneeded = (event) => {
  4. const db = event.target.result;
  5. // 创建对象仓库(若不存在)
  6. if (!db.objectStoreNames.contains('customers')) {
  7. const store = db.createObjectStore('customers', {
  8. keyPath: 'id',
  9. autoIncrement: true
  10. });
  11. // 创建索引
  12. store.createIndex('name', 'name', { unique: false });
  13. store.createIndex('email', 'email', { unique: true });
  14. }
  15. };
  16. request.onsuccess = (event) => {
  17. const db = event.target.result;
  18. console.log('数据库打开成功', db.name);
  19. };
  20. request.onerror = (event) => {
  21. console.error('数据库打开失败', event.target.error);
  22. };

关键点说明:

  • indexedDB.open()接受数据库名和版本号参数
  • onupgradeneeded事件仅在版本更新时触发
  • 对象仓库的keyPath定义主键,可设为属性名或autoIncrement

2.2 CRUD操作实现

数据添加

  1. function addCustomer(db, customer) {
  2. const transaction = db.transaction(['customers'], 'readwrite');
  3. const store = transaction.objectStore('customers');
  4. const request = store.add(customer);
  5. request.onsuccess = () => {
  6. console.log('客户添加成功');
  7. };
  8. request.onerror = () => {
  9. console.error('添加失败', request.error);
  10. };
  11. }

数据查询

  1. // 通过主键查询
  2. function getCustomerById(db, id) {
  3. return new Promise((resolve, reject) => {
  4. const transaction = db.transaction(['customers'], 'readonly');
  5. const store = transaction.objectStore('customers');
  6. const request = store.get(id);
  7. request.onsuccess = () => resolve(request.result);
  8. request.onerror = () => reject(request.error);
  9. });
  10. }
  11. // 通过索引查询
  12. function getCustomersByName(db, name) {
  13. return new Promise((resolve, reject) => {
  14. const transaction = db.transaction(['customers'], 'readonly');
  15. const store = transaction.objectStore('customers');
  16. const index = store.index('name');
  17. const request = index.getAll(name);
  18. request.onsuccess = () => resolve(request.result);
  19. request.onerror = () => reject(request.error);
  20. });
  21. }

数据更新与删除

  1. // 更新数据
  2. function updateCustomer(db, customer) {
  3. const transaction = db.transaction(['customers'], 'readwrite');
  4. const store = transaction.objectStore('customers');
  5. const request = store.put(customer); // put方法同时支持新增和更新
  6. return new Promise((resolve, reject) => {
  7. request.onsuccess = () => resolve(true);
  8. request.onerror = () => reject(request.error);
  9. });
  10. }
  11. // 删除数据
  12. function deleteCustomer(db, id) {
  13. const transaction = db.transaction(['customers'], 'readwrite');
  14. const store = transaction.objectStore('customers');
  15. const request = store.delete(id);
  16. return new Promise((resolve, reject) => {
  17. request.onsuccess = () => resolve(true);
  18. request.onerror = () => reject(request.error);
  19. });
  20. }

三、高级应用技巧

3.1 事务管理最佳实践

  1. // 多仓库事务示例
  2. function transferFunds(db, fromId, toId, amount) {
  3. return new Promise((resolve, reject) => {
  4. const transaction = db.transaction(['accounts', 'transactions'], 'readwrite');
  5. // 显式处理事务完成
  6. transaction.oncomplete = () => resolve(true);
  7. transaction.onabort = () => reject(transaction.error);
  8. transaction.onerror = () => reject(transaction.error);
  9. const accounts = transaction.objectStore('accounts');
  10. const txStore = transaction.objectStore('transactions');
  11. // 获取转出账户
  12. const fromRequest = accounts.get(fromId);
  13. fromRequest.onsuccess = () => {
  14. const fromAccount = fromRequest.result;
  15. if (fromAccount.balance < amount) {
  16. transaction.abort(); // 余额不足时中止事务
  17. return;
  18. }
  19. // 更新账户
  20. fromAccount.balance -= amount;
  21. accounts.put(fromAccount);
  22. // 获取转入账户并更新
  23. const toRequest = accounts.get(toId);
  24. toRequest.onsuccess = () => {
  25. const toAccount = toRequest.result;
  26. toAccount.balance += amount;
  27. accounts.put(toAccount);
  28. // 记录交易
  29. txStore.add({
  30. from: fromId,
  31. to: toId,
  32. amount,
  33. timestamp: new Date()
  34. });
  35. };
  36. };
  37. });
  38. }

3.2 游标的高级使用

  1. // 分页查询实现
  2. function getCustomersPage(db, pageSize, pageNumber) {
  3. return new Promise((resolve, reject) => {
  4. const offset = (pageNumber - 1) * pageSize;
  5. const results = [];
  6. let count = 0;
  7. const transaction = db.transaction(['customers'], 'readonly');
  8. const store = transaction.objectStore('customers');
  9. const request = store.openCursor();
  10. request.onsuccess = (event) => {
  11. const cursor = event.target.result;
  12. if (cursor) {
  13. if (count >= offset && count < offset + pageSize) {
  14. results.push(cursor.value);
  15. }
  16. count++;
  17. cursor.continue();
  18. } else {
  19. resolve(results);
  20. }
  21. };
  22. request.onerror = () => reject(request.error);
  23. });
  24. }

3.3 性能优化策略

  1. 批量操作:使用IDBTransaction的批量提交能力
  2. 索引优化:为高频查询字段创建索引,避免全表扫描
  3. 内存管理:处理大量数据时使用游标而非getAll()
  4. 版本控制:合理设计数据库版本升级策略
  5. 错误处理:实现完善的错误捕获和恢复机制

四、实际应用场景

4.1 离线应用实现

  1. // 检测网络状态并同步数据
  2. class OfflineManager {
  3. constructor(dbName) {
  4. this.db = null;
  5. this.queue = [];
  6. this.isOnline = navigator.onLine;
  7. window.addEventListener('online', () => this.syncData());
  8. window.addEventListener('offline', () => this.isOnline = false);
  9. indexedDB.open(dbName, 1).onsuccess = (e) => {
  10. this.db = e.target.result;
  11. };
  12. }
  13. addOfflineOperation(operation, data) {
  14. this.queue.push({ operation, data });
  15. if (this.isOnline) {
  16. this.syncData();
  17. }
  18. }
  19. async syncData() {
  20. this.isOnline = true;
  21. while (this.queue.length > 0) {
  22. const { operation, data } = this.queue.shift();
  23. try {
  24. switch (operation) {
  25. case 'add':
  26. await this.executeAdd(data);
  27. break;
  28. // 其他操作...
  29. }
  30. } catch (error) {
  31. console.error('同步失败,重新加入队列', error);
  32. this.queue.unshift({ operation, data });
  33. break;
  34. }
  35. }
  36. }
  37. async executeAdd(data) {
  38. const transaction = this.db.transaction(['data'], 'readwrite');
  39. const store = transaction.objectStore('data');
  40. await new Promise((resolve, reject) => {
  41. const request = store.add(data);
  42. request.onsuccess = resolve;
  43. request.onerror = reject;
  44. });
  45. }
  46. }

4.2 大文件存储方案

对于超过存储限制的大文件,可采用分块存储策略:

  1. // 分块上传实现
  2. async function uploadLargeFile(db, file, chunkSize = 1024 * 1024) { // 默认1MB分块
  3. const fileId = generateFileId();
  4. const totalChunks = Math.ceil(file.size / chunkSize);
  5. // 存储文件元数据
  6. const metaTransaction = db.transaction(['files'], 'readwrite');
  7. await new Promise((resolve, reject) => {
  8. const request = metaTransaction.objectStore('files').add({
  9. id: fileId,
  10. name: file.name,
  11. size: file.size,
  12. type: file.type,
  13. chunks: totalChunks,
  14. uploaded: 0
  15. });
  16. request.onsuccess = resolve;
  17. request.onerror = reject;
  18. });
  19. // 分块上传
  20. for (let i = 0; i < totalChunks; i++) {
  21. const start = i * chunkSize;
  22. const end = Math.min(start + chunkSize, file.size);
  23. const chunk = file.slice(start, end);
  24. const chunkTransaction = db.transaction(['chunks'], 'readwrite');
  25. await new Promise((resolve, reject) => {
  26. const reader = new FileReader();
  27. reader.onload = (e) => {
  28. const request = chunkTransaction.objectStore('chunks').add({
  29. fileId,
  30. chunkIndex: i,
  31. data: e.target.result
  32. });
  33. request.onsuccess = () => {
  34. // 更新元数据
  35. updateUploadedCount(db, fileId, i + 1).then(resolve).catch(reject);
  36. };
  37. request.onerror = reject;
  38. };
  39. reader.onerror = reject;
  40. reader.readAsArrayBuffer(chunk);
  41. });
  42. }
  43. }
  44. async function updateUploadedCount(db, fileId, count) {
  45. return new Promise((resolve, reject) => {
  46. const transaction = db.transaction(['files'], 'readwrite');
  47. const store = transaction.objectStore('files');
  48. const request = store.get(fileId);
  49. request.onsuccess = () => {
  50. const file = request.result;
  51. file.uploaded = count;
  52. const updateRequest = store.put(file);
  53. updateRequest.onsuccess = resolve;
  54. updateRequest.onerror = reject;
  55. };
  56. request.onerror = reject;
  57. });
  58. }

五、常见问题解决方案

5.1 跨浏览器兼容处理

  1. // 检测IndexedDB支持
  2. function isIndexedDBSupported() {
  3. try {
  4. return 'indexedDB' in window ||
  5. 'webkitIndexedDB' in window ||
  6. 'mozIndexedDB' in window ||
  7. 'msIndexedDB' in window;
  8. } catch (e) {
  9. return false;
  10. }
  11. }
  12. // 创建兼容性封装
  13. const indexedDB = window.indexedDB ||
  14. window.webkitIndexedDB ||
  15. window.mozIndexedDB ||
  16. window.msIndexedDB;
  17. const IDBTransaction = window.IDBTransaction ||
  18. window.webkitIDBTransaction ||
  19. window.msIDBTransaction;
  20. const IDBKeyRange = window.IDBKeyRange ||
  21. window.webkitIDBKeyRange ||
  22. window.msIDBKeyRange;

5.2 存储空间限制处理

  1. // 检测存储配额
  2. async function checkStorageQuota() {
  3. if (navigator.storage && navigator.storage.estimate) {
  4. const { quota, usage } = await navigator.storage.estimate();
  5. return {
  6. available: quota - usage,
  7. percentageUsed: (usage / quota) * 100
  8. };
  9. }
  10. return { available: Infinity, percentageUsed: 0 };
  11. }
  12. // 请求额外存储空间(Chrome)
  13. async function requestMoreStorage() {
  14. if (navigator.webkitPersistentStorage &&
  15. typeof navigator.webkitPersistentStorage.requestQuota === 'function') {
  16. try {
  17. const newQuota = await navigator.webkitPersistentStorage.requestQuota(
  18. 1024 * 1024 * 1024 // 请求1GB额外空间
  19. );
  20. return newQuota;
  21. } catch (e) {
  22. console.error('配额请求失败', e);
  23. return 0;
  24. }
  25. }
  26. return 0;
  27. }

5.3 数据库备份与恢复

  1. // 导出数据库
  2. async function exportDatabase(dbName) {
  3. return new Promise(async (resolve, reject) => {
  4. const db = await new Promise((res, rej) => {
  5. const request = indexedDB.open(dbName);
  6. request.onsuccess = () => res(request.result);
  7. request.onerror = () => rej(request.error);
  8. });
  9. const exportData = {};
  10. // 遍历所有对象仓库
  11. for (const storeName of db.objectStoreNames) {
  12. exportData[storeName] = await new Promise((res, rej) => {
  13. const transaction = db.transaction(storeName, 'readonly');
  14. const store = transaction.objectStore(storeName);
  15. const request = store.getAll();
  16. request.onsuccess = () => res(request.result);
  17. request.onerror = () => rej(request.error);
  18. });
  19. }
  20. resolve(exportData);
  21. });
  22. }
  23. // 导入数据库
  24. async function importDatabase(dbName, importData) {
  25. return new Promise(async (resolve, reject) => {
  26. const request = indexedDB.deleteDatabase(dbName);
  27. request.onsuccess = async () => {
  28. const db = await new Promise((res, rej) => {
  29. const openRequest = indexedDB.open(dbName, 1);
  30. openRequest.onupgradeneeded = (e) => {
  31. const db = e.target.result;
  32. for (const storeName in importData) {
  33. if (!db.objectStoreNames.contains(storeName)) {
  34. db.createObjectStore(storeName, {
  35. keyPath: 'id',
  36. autoIncrement: true
  37. });
  38. }
  39. }
  40. };
  41. openRequest.onsuccess = () => res(openRequest.result);
  42. openRequest.onerror = () => rej(openRequest.error);
  43. });
  44. // 导入数据
  45. for (const storeName in importData) {
  46. const transaction = db.transaction(storeName, 'readwrite');
  47. const store = transaction.objectStore(storeName);
  48. for (const item of importData[storeName]) {
  49. await new Promise((res, rej) => {
  50. const request = store.put(item);
  51. request.onsuccess = res;
  52. request.onerror = rej;
  53. });
  54. }
  55. }
  56. resolve(true);
  57. };
  58. request.onerror = () => reject(request.error);
  59. });
  60. }

六、最佳实践总结

  1. 架构设计

    • 合理规划对象仓库结构
    • 为高频查询字段创建索引
    • 考虑使用版本号管理数据库结构变更
  2. 性能优化

    • 批量操作替代单条操作
    • 使用游标处理大数据集
    • 避免在事务中执行耗时操作
  3. 错误处理

    • 实现完善的错误捕获机制
    • 设计数据恢复策略
    • 处理存储空间不足的情况
  4. 安全考虑

    • 敏感数据加密存储
    • 实现访问控制机制
    • 定期清理过期数据
  5. 测试策略

    • 跨浏览器兼容性测试
    • 大数据量压力测试
    • 离线场景功能测试

通过系统掌握IndexedDB的核心概念和操作方法,开发者可以构建出具备强大离线能力的Web应用,显著提升用户体验和应用可靠性。本教学提供的完整代码示例和最佳实践,能够帮助开发者快速上手并避免常见陷阱,在实际项目中高效应用IndexedDB技术。

相关文章推荐

发表评论

活动