Skip to content

IndexedDB - 游标

尽管 get() 和 getAll() 等方法非常适合检索特定记录或完整数据集,但如果数据集非常大,它们可能会效率低下,因为 getAll() 会将所有记录加载到内存中。游标 (Cursor) 提供了一种强大且内存高效的方式,可以逐个迭代对象仓库 (object store) 或索引 (index) 中的记录。

游标就像一个指针,它在您的记录中移动。您打开一个游标,处理游标当前位置的记录,然后指示它前进到下一条。

现代替代方案:getAll() 和 getAllKeys()

Section titled “现代替代方案:getAll() 和 getAllKeys()”

在深入了解游标之前,请始终考虑是否有更简单的方法适合您的需求。对于中等大小的数据集,getAll() 通常就足够了,并且可以使代码更简单。

// Get all objects from the store
const allRecords = await store.getAll();
// Get all keys from the store
const allKeys = await store.getAllKeys();

在以下情况下使用游标:

  • 迭代大量记录而不会占用过多内存。
  • 根据特定逻辑逐个处理、更新或删除记录。
  • 实现仅靠 getAll() 和键范围无法完成的复杂查询。

您可以在对象仓库 (object store) 或索引 (index) 上使用 openCursor() 方法创建游标。该方法是异步的,并返回一个 IDBRequest 对象。

objectStore.openCursor(range, direction);
  • range(可选): 一个 IDBKeyRange,用于将迭代限制在特定键。如果为 null,则迭代整个存储。
  • direction(可选): 一个字符串,指定迭代方向。可以是 'next'(默认)、'prev'、'nextunique' 或 'prevunique'。

这是一个完整的 HTML 示例,展示了如何使用游标从对象仓库中读取所有记录。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>IndexedDB Cursors</title>
</head>
<body>
<h1>Reading data with a cursor. Check the console.</h1>
<ul id="user-list"></ul>
<script>
const DB_NAME = "UserDB";
const STORE_NAME = "users";
const userList = document.getElementById('user-list');
// --- 数据库设置(为简洁起见已省略,假定已设置)---
async function setupAndPopulate() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onupgradeneeded = e => {
const db = e.target.result;
const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
store.createIndex('by_name', 'name', { unique: false });
// Populate with sample data
store.put({ id: 1, name: 'Alice', role: 'Admin' });
store.put({ id: 2, name: 'Bob', role: 'Editor' });
store.put({ id: 3, name: 'Charlie', role: 'Viewer' });
store.put({ id: 4, name: 'Diana', role: 'Editor' });
};
request.onsuccess = e => resolve(e.target.result);
request.onerror = e => reject(e.target.error);
});
}
async function iterateWithCursor(db) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.openCursor();
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
// We have a record
const user = cursor.value;
console.log(`Key: ${cursor.key}, Value:`, user);
// Display it in the UI
const li = document.createElement('li');
li.textContent = `ID: ${user.id}, Name: ${user.name}, Role: ${user.role}`;
userList.appendChild(li);
// Move to the next record
cursor.continue();
} else {
// No more records
console.log('Cursor finished.');
resolve();
}
};
request.onerror = event => {
reject(event.target.error);
};
});
}
async function main() {
// Clear previous DB for a clean run
indexedDB.deleteDatabase(DB_NAME).onsuccess = async () => {
const db = await setupAndPopulate();
await iterateWithCursor(db);
db.close();
}
}
main();
</script>
</body>
</html>
  • 更新记录: 在 'readwrite' 事务中,您可以调用 cursor.update(newValue) 来修改游标当前位置的记录。
  • 删除记录: 类似地,在 'readwrite' 事务中,cursor.delete() 将移除当前记录。

例如,要将所有“Editor”角色更新为“Contributor”:

const request = store.openCursor();
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.role === 'Editor') {
const updatedUser = { ...cursor.value, role: 'Contributor' };
cursor.update(updatedUser);
}
cursor.continue();
}
};