IndexedDB 使用指南

前言

在上一篇文章介绍了前端跨页面通讯终极指南⑧:Cookie 用法全解析,下一篇是介绍前端跨页面通讯终极指南⑨:# IndexedDB 用法全解析,考虑到这种方式并不是常用,先介绍下IndexedDB的使用方法,再对跨页面通信进行总结。

下面介绍下IndexedDB概念以及常见用法。

1. 基本概念

IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。

IndexedDB是浏览器提供的 NoSQL 数据库,用于在客户端存储大量结构化数据。它支持事务、索引查询和异步操作,适合离线应用、数据缓存等场景。

数据库层级结构

scss 复制代码
Database (数据库)
├── Object Store (对象存储空间)
│   ├── Index (索引)
│   └── Data Records (数据记录)
└── Object Store (对象存储空间)

关键特性

  • 异步操作:所有操作都是异步的,使用事件或Promise
  • 事务支持:原子性操作,要么全部成功要么全部回滚
  • 索引查询:支持基于索引的高效查询
  • 存储限制:通常限制为几百MB(取决于浏览器)
  • 持久化存储:数据在浏览器关闭后仍然保留

2. 核心API

2.1 打开数据库

javascript 复制代码
const request = indexedDB.open('dbName', version);

2.2 创建/升级数据库

javascript 复制代码
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // 创建或升级存储空间
};

2.3 事务操作

javascript 复制代码
const transaction = db.transaction(storeNames, mode);
const store = transaction.objectStore(storeName);

这里的事务需要特别说明下。为什么要引入事务?

我们用银行转账的案例进行说明:

假设你要从 A账户 转账 100 元给 B账户。这个过程包含两个必不可少的步骤:

  1. 从 A 账户扣除 100 元。
  2. 给 B 账户增加 100 元。

如果没有事务(不安全的情况): 如果在步骤 1 完成后,突然停电了或系统出错了,步骤 2 没执行。结果就是:A 的钱少了,B 没收到钱,这 100 元凭空消失了。

有了事务(安全的情况): 当你点击"确认转账"时,数据库开启了一个"事务":

  • 原子性 :数据库将步骤 1 和步骤 2 打包成一个整体。只有当两个步骤成功时,修改才会生效;只要中间任意一步出错,数据库会自动"回滚",让一切回到转账前的样子(A 的钱没少,B 的钱没多)。

对应到 IndexedDB

  • 银行 -> 整个 IndexedDB 数据库 (db)
  • A账户B账户 -> 相同的对象存储 (accounts)
  • 转账这个行为 -> 一个事务 (transaction)

现在我们来看代码如何实现这个逻辑:

js 复制代码
// 1. 开启事务
// 参数 ['accounts'] 指定了事务的【作用域】:只允许操作 'accounts' 这个表
// 参数 'readwrite' 指定了【模式】:允许修改数据(读写)
const transaction = db.transaction(['accounts'], 'readwrite');
// 2. 获取对象仓库(表)
const store = transaction.objectStore('accounts');
// --- 以下是事务内的具体操作 ---
// 操作 A:更新账户 A 的余额(假设原为 1000,现改为 900)
store.put({ id: 'A', balance: 900 });
// 操作 B:更新账户 B 的余额(假设原为 1000,现改为 1100)
store.put({ id: 'B', balance: 1100 });
// --- 监听事务结果 ---
// 如果上面所有操作都成功
transaction.oncomplete = function(event) {
    console.log("转账成功!数据已永久保存。");
};
// 如果中间任何一步报错(比如账户 B 不存在,或者磁盘满了)
transaction.onerror = function(event) {
    console.log("转账失败!刚才的修改全部撤销,A 的余额变回 1000。");
};

通过这个例子我们可以清楚的看到IndexedDB事务的核心作用:

  • 原子性:将单个或多个相关的数据库操作(扣款、存款)打包成一个不可分割的整体。
  • 一致性:确保数据库从一个正确的状态(转账前)转换到另一个正确的状态(转账后)。如果中途失败,则会回到初始状态,不会出现数据不一致(钱少了)的中间状态。

2.4. 常用操作方法

javascript 复制代码
// 添加数据
store.add(data);

// 更新数据
store.put(data);

// 删除数据
store.delete(key);

// 获取数据
store.get(key);

// 获取所有数据
store.getAll();

// 使用索引查询
store.index('indexName').get(value);

3. 数据库操作

3.1 打开数据库

javascript 复制代码
const openDB = (dbName, version) => {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(dbName, version);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;
      // 数据库升级逻辑
    };
  });
};

3.2 关闭数据库

javascript 复制代码
db.close();

3.3 删除数据库

javascript 复制代码
const deleteDB = (dbName) => {
  return new Promise((resolve, reject) => {
    const request = indexedDB.deleteDatabase(dbName);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve('数据库删除成功');
  });
};

4. 事务处理

4.1 创建事务

javascript 复制代码
const transaction = db.transaction(['store1', 'store2'], 'readwrite');

4.2 事务模式

  • 'readonly':只读事务
  • 'readwrite':读写事务
  • 'versionchange':版本变更事务

4.3 事务错误处理

javascript 复制代码
transaction.onerror = (event) => {
  console.error('事务错误:', event.target.error);
};

transaction.onabort = () => {
  console.log('事务已回滚');
};

transaction.oncomplete = () => {
  console.log('事务完成');
};

4.4 手动回滚事务

javascript 复制代码
transaction.abort();

5. 数据存储

5.1 创建对象存储空间

javascript 复制代码
const store = db.createObjectStore('users', {
  keyPath: 'id', // 使用数据中的id字段作为主键
  autoIncrement: true // 自动生成主键
});

5.2 添加数据

javascript 复制代码
const addData = (store, data) => {
  return new Promise((resolve, reject) => {
    const request = store.add(data);

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

5.3 更新数据

javascript 复制代码
const updateData = (store, data) => {
  return new Promise((resolve, reject) => {
    const request = store.put(data);

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

5.4 删除数据

javascript 复制代码
const deleteData = (store, key) => {
  return new Promise((resolve, reject) => {
    const request = store.delete(key);

    request.onsuccess = () => resolve();
    request.onerror = () => reject(request.error);
  });
};

6. 查询操作

6.1 获取单个数据

javascript 复制代码
const getData = (store, key) => {
  return new Promise((resolve, reject) => {
    const request = store.get(key);

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

6.2 获取所有数据

javascript 复制代码
const getAllData = (store) => {
  return new Promise((resolve, reject) => {
    const request = store.getAll();

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

6.3 范围查询

javascript 复制代码
const rangeQuery = (store, range) => {
  return new Promise((resolve, reject) => {
    const request = store.openCursor(range);

    const results = [];
    request.onsuccess = (event) => {
      const cursor = event.target.result;
      if (cursor) {
        results.push(cursor.value);
        cursor.continue();
      } else {
        resolve(results);
      }
    };

    request.onerror = () => reject(request.error);
  });
};

7. 索引使用

7.1 创建索引

javascript 复制代码
store.createIndex('nameIndex', 'name', { unique: false });
store.createIndex('emailIndex', 'email', { unique: true });

7.2 使用索引查询

javascript 复制代码
const queryByIndex = (store, indexName, value) => {
  return new Promise((resolve, reject) => {
    const index = store.index(indexName);
    const request = index.get(value);

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

7.3 范围索引查询

javascript 复制代码
const rangeQueryByIndex = (store, indexName, range) => {
  return new Promise((resolve, reject) => {
    const index = store.index(indexName);
    const request = index.openCursor(range);

    const results = [];
    request.onsuccess = (event) => {
      const cursor = event.target.result;
      if (cursor) {
        results.push(cursor.value);
        cursor.continue();
      } else {
        resolve(results);
      }
    };

    request.onerror = () => reject(request.error);
  });
};

8. 完整示例

用户管理系统

javascript 复制代码
// 用户管理系统
class UserManager {
  constructor(dbName = 'UserDB', version = 1) {
    this.dbName = dbName;
    this.version = version;
    this.db = null;
  }

  async init() {
    this.db = await this.openDB();
    return this;
  }

  async openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);

      request.onupgradeneeded = (event) => {
        const db = event.target.result;

        if (!db.objectStoreNames.contains('users')) {
          const store = db.createObjectStore('users', { keyPath: 'id' });
          store.createIndex('name', 'name', { unique: false });
          store.createIndex('email', 'email', { unique: true });
          store.createIndex('createdAt', 'createdAt', { unique: false });
        }
      };
    });
  }

  async addUser(user) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['users'], 'readwrite');
      const store = transaction.objectStore('users');

      user.createdAt = new Date().toISOString();

      const request = store.add(user);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async getUserById(id) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['users'], 'readonly');
      const store = transaction.objectStore('users');
      const request = store.get(id);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async getUserByEmail(email) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['users'], 'readonly');
      const store = transaction.objectStore('users');
      const index = store.index('email');
      const request = index.get(email);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async updateUser(id, updates) {
    const user = await this.getUserById(id);
    if (!user) throw new Error('用户不存在');

    const updatedUser = { ...user, ...updates };
    return this.putUser(updatedUser);
  }

  async putUser(user) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['users'], 'readwrite');
      const store = transaction.objectStore('users');
      const request = store.put(user);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async deleteUser(id) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['users'], 'readwrite');
      const store = transaction.objectStore('users');
      const request = store.delete(id);

      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }

  async getAllUsers() {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['users'], 'readonly');
      const store = transaction.objectStore('users');
      const request = store.getAll();

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async searchUsers(query) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['users'], 'readonly');
      const store = transaction.objectStore('users');
      const index = store.index('name');

      const range = IDBKeyRange.bound(query, query + '\uffff');
      const request = index.openCursor(range);

      const results = [];
      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor) {
          results.push(cursor.value);
          cursor.continue();
        } else {
          resolve(results);
        }
      };

      request.onerror = () => reject(request.error);
    });
  }
}

// 使用示例
async function demo() {
  const userManager = new UserManager();
  await userManager.init();

  // 添加用户
  await userManager.addUser({
    id: 'user1',
    name: '张三',
    email: 'zhangsan@example.com',
    age: 25
  });

  // 获取用户
  const user = await userManager.getUserById('user1');
  console.log('获取用户:', user);

  // 更新用户
  await userManager.updateUser('user1', { age: 26 });

  // 搜索用户
  const users = await userManager.searchUsers('张');
  console.log('搜索结果:', users);

  // 删除用户
  await userManager.deleteUser('user1');
}

总结

最后总结一下:IndexedDB是前端的数据库,通常在 Web Storage 无法满足容量要求的场景下才使用,它能够存储大量数据,一般不轻易用。

相关推荐
ZC跨境爬虫3 小时前
海南大学交友平台登录页开发实战day3(解决python传输并读取登录信息的问题)
前端·数据库·python·html
kgduu3 小时前
react源码学习之reconcile
前端·学习·react.js
远方的小草3 小时前
Cursor(vscode) debug for Chrome
前端
远方的小草3 小时前
线上调试代码,试试SourceMap?
前端
whuhewei3 小时前
React Fiber架构
前端·react.js·架构
陆枫Larry3 小时前
微信小程序:如何优雅地修改富文本(u-parse/rich-text)内部样式?
前端
远方的小草3 小时前
Nginx 反向代理
前端
英俊潇洒美少年3 小时前
通用构建优化(编译阶段)+ Vue 专属运行时优化 + React 专属运行时优化
前端·vue.js·react.js
慕斯fuafua3 小时前
CSS——样式
前端·css
英俊潇洒美少年3 小时前
Vue 和 React 的核心渲染机制 对比
前端·vue.js·react.js