普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月28日首页

IndexedDB 使用指南

2025年12月28日 19:40

前言

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

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

1. 基本概念

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

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

数据库层级结构

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

关键特性

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

2. 核心API

2.1 打开数据库

const request = indexedDB.open('dbName', version);

2.2 创建/升级数据库

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

2.3 事务操作

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)

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

// 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. 常用操作方法

// 添加数据
store.add(data);

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

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

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

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

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

3. 数据库操作

3.1 打开数据库

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 关闭数据库

db.close();

3.3 删除数据库

const deleteDB = (dbName) => {
  return new Promise((resolve, reject) => {
    const request = indexedDB.deleteDatabase(dbName);

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

4. 事务处理

4.1 创建事务

const transaction = db.transaction(['store1', 'store2'], 'readwrite');

4.2 事务模式

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

4.3 事务错误处理

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

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

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

4.4 手动回滚事务

transaction.abort();

5. 数据存储

5.1 创建对象存储空间

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

5.2 添加数据

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 更新数据

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 删除数据

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 获取单个数据

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 获取所有数据

const getAllData = (store) => {
  return new Promise((resolve, reject) => {
    const request = store.getAll();

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

6.3 范围查询

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 创建索引

store.createIndex('nameIndex', 'name', { unique: false });
store.createIndex('emailIndex', 'email', { unique: true });

7.2 使用索引查询

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 范围索引查询

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. 完整示例

用户管理系统

// 用户管理系统
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');
}

image.png

总结

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

❌
❌