
indexeddb是一个强大的客户端结构化数据存储方案,其核心概念是数据库(database)和对象存储(object store)。对象存储类似于关系型数据库中的表,用于存储键值对数据。indexeddb的模式(schema)管理严格,任何对数据库结构的修改,例如创建、删除或修改对象存储,都必须在idbopendbrequest的onupgradeneeded回调函数中进行。
onupgradeneeded事件只在以下两种情况被触发:
这意味着,如果需要添加新的对象存储,就必须通过递增数据库版本号来触发此事件。在onsuccess回调中尝试调用db.createObjectStore()会导致运行时错误,因为此时数据库连接已处于稳定状态,不允许进行模式修改。
开发者常常会遇到一种需求:希望在运行时根据不同的“命名空间”或“类型”动态地创建或使用不同的对象存储,例如实现一个类似于localStorage但支持多个独立存储空间的异步版本。
class LocalStorageAsync {
#database;
constructor(storeName = 'default') {
const dbName = 'LocalStorageAsyncDB';
// 首次打开或版本升级时处理模式
const openRequest = indexedDB.open(dbName, 1); // 初始版本号为1
openRequest.onupgradeneeded = (event) => {
const db = event.target.result;
// 在这里创建或修改对象存储
if (!db.objectStoreNames.contains('dataStore')) {
db.createObjectStore('dataStore', { keyPath: 'id', autoIncrement: true });
}
};
this.#database = new Promise((resolve, reject) => {
openRequest.onsuccess = (event) => {
const db = event.target.result;
// 在这里不能创建新的对象存储
// if (!db.objectStoreNames.contains(storeName)) {
// db.createObjectStore(storeName); // 错误:不能在 onsuccess 中调用
// }
resolve(db);
};
openRequest.onerror = (event) => {
console.error("IndexedDB error:", event.target.errorCode);
reject(event.target.error);
};
});
}
// getItem, setItem 等方法将操作 #database
async getItem(storeName, key) {
const db = await this.#database;
const transaction = db.transaction(['dataStore'], 'readonly');
const store = transaction.objectStore('dataStore');
// 假设数据结构为 { id: 'some_key', storeName: 'foo', value: 'bar' }
const request = store.get(key); // 如果key是复合的,需要索引或遍历
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
const item = event.target.result;
// 筛选出特定storeName的数据
if (item && item.storeName === storeName) {
resolve(item.value);
} else {
resolve(null);
}
};
request.onerror = (event) => reject(event.target.error);
});
}
async setItem(storeName, key, value) {
const db = await this.#database;
const transaction = db.transaction(['dataStore'], 'readwrite');
const store = transaction.objectStore('dataStore');
// 存储时带上 storeName 属性
const dataToStore = { id: key, storeName: storeName, value: value };
const request = store.put(dataToStore);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = (event) => reject(event.target.error);
});
}
}
// 示例使用
async function testLocalStorageAsync() {
const defaultStore = new LocalStorageAsync('default');
await defaultStore.setItem('default', 'myKey', 'myValue');
console.log('Default store value:', await defaultStore.getItem('default', 'myKey'));
const fooStore = new LocalStorageAsync('foo'); // 此时不会创建新的对象存储
await fooStore.setItem('foo', 'anotherKey', 'anotherValue');
console.log('Foo store value:', await fooStore.getItem('foo', 'anotherKey'));
}
testLocalStorageAsync();正如问题中提到的,尝试在onsuccess回调中通过“虚构”的bumpVersion方法来触发onupgradeneeded是不可能的。IndexedDB的设计哲学是模式(Schema)相对稳定,不应频繁变动。为了实现数据分区而避免频繁修改模式,推荐的策略是在单个或少数几个对象存储内部进行数据管理:
使用数据属性进行分区: 这是最推荐和灵活的方法。创建一个或少数几个通用的对象存储(例如,命名为dataStore)。在存储每个数据项时,为其添加一个额外的属性(如storeName或type),用于标识该数据所属的逻辑分区。
示例代码(基于上述LocalStorageAsync的优化): 在LocalStorageAsync的constructor中,我们只创建了一个名为dataStore的对象存储。getItem和setItem方法则通过在存储的数据对象中添加storeName属性来区分不同的逻辑存储。为了高效查询,建议为storeName属性创建索引。
// 在 onupgradeneeded 中为 storeName 创建索引
openRequest.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('dataStore')) {
const store = db.createObjectStore('dataStore', { keyPath: 'id' });
// 为 storeName 属性创建索引,允许重复值
store.createIndex('byStoreName', 'storeName', { unique: false });
}
};
// ... getItem 方法中使用索引
async getItem(storeName, key) {
const db = await this.#database;
const transaction = db.transaction(['dataStore'], 'readonly');
const store = transaction.objectStore('dataStore');
const index = store.index('byStoreName'); // 获取索引
// 使用索引和 keyPath 查询
const request = index.get(IDBKeyRange.only(storeName)); // 查询特定 storeName 的数据
// 注意:这里需要进一步过滤 key,因为 index.get(storeName) 可能会返回多个结果
// 更直接的方式是使用复合键或在应用程序层过滤
// For a simple key-value store, we might need to iterate or rely on a composite key if possible
// A simple get(key) followed by a storeName check is often sufficient for simple cases.
const directRequest = store.get(key); // 先按主键获取
return new Promise((resolve, reject) => {
directRequest.onsuccess = (event) => {
const item = event.target.result;
if (item && item.storeName === storeName) {
resolve(item.value);
} else {
resolve(null);
}
};
directRequest.onerror = (event) => reject(event.target.error);
});
}
// ... setItem 方法保持不变使用复合键(Compound Keys): 如果你的数据模型允许,可以将storeName和key组合成一个复合键作为主键。这样可以直接通过复合键进行高效查找。
示例:
// 在 onupgradeneeded 中创建对象存储时,不指定 keyPath,而是手动管理键
const store = db.createObjectStore('dataStore'); // 无 keyPath
// setItem 时
async setItem(storeName, key, value) {
const db = await this.#database;
const transaction = db.transaction(['dataStore'], 'readwrite');
const store = transaction.objectStore('dataStore');
const compositeKey = [storeName, key]; // 复合键
const dataToStore = { value: value }; // 实际存储的数据
const request = store.put(dataToStore, compositeKey); // 使用复合键存储
// ...
}
// getItem 时
async getItem(storeName, key) {
const db = await this.#database;
const transaction = db.transaction(['dataStore'], 'readonly');
const store = transaction.objectStore('dataStore');
const compositeKey = [storeName, key];
const request = store.get(compositeKey); // 使用复合键查询
// ...
}请注意,IndexedDB的复合键要求键是数组,并且数组中的每个元素都是有效的键类型。
通过采纳上述策略,开发者可以在不频繁修改IndexedDB模式的前提下,灵活地管理和分区应用程序数据,从而构建更稳定、高效的Web应用。
以上就是IndexedDB:管理动态对象存储与数据分区策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号