
indexeddb是一个强大的客户端存储解决方案,但其设计哲学与传统的文件系统或键值存储有所不同。在indexeddb中,idbdatabase.createobjectstore() 方法用于创建新的对象存储(object store),但此方法并非在任何时候都可以调用。它有一个严格的限制:只能在 idbopendbrequest 的 onupgradeneeded 回调函数中被调用。
onupgradeneeded 事件仅在以下两种情况下触发:
这意味着,如果你的数据库已经存在且版本号没有变化,你将无法在 onsuccess 回调或其他运行时上下文中动态地创建新的对象存储。尝试在 onsuccess 中调用 db.createObjectStore() 会导致运行时错误,因为此时数据库的事务模式不允许进行模式修改操作。
原始问题中尝试在 onsuccess 中检查 db.objectStoreNames.contains(storeKey) 并调用 createObjectStore 的做法,以及尝试通过“假装”升级版本(例如 db.bumpVersion(db.version + 1))来触发 onupgradeneeded,都是不可行的。IndexedDB 没有提供直接在 onsuccess 中触发版本升级的API。
从数据库设计的角度来看,频繁地修改数据库模式(Schema)通常是不推荐的。数据库模式(例如,创建、删除或修改表/对象存储)应该是相对稳定的。onupgradeneeded 回调的目的是处理数据库版本升级时的模式迁移,例如添加新的对象存储、创建索引或删除旧的存储。将每次数据逻辑分区都映射为一个新的对象存储,会导致以下问题:
为了实现类似 localStorage 的多分区异步存储,同时避免频繁修改IndexedDB模式,最佳实践是在一个或少数几个固定的对象存储中,通过数据内部的属性或键前缀来区分不同的逻辑分区。
例如,你可以创建一个名为 data 的单一对象存储。当需要存储属于不同“逻辑存储”的数据时,可以在存储的数据对象中添加一个 storeName 属性,或者在键本身添加一个前缀。
以下是一个基于此思想的 LocalStorageAsync 实现示例:
class LocalStorageAsync {
#database;
#logicalStoreName; // 用于区分逻辑分区的名称
static #DB_NAME = 'LocalStorageAsyncDB'; // 数据库名称
static #PHYSICAL_OBJECT_STORE = 'key_value_data'; // 唯一的物理对象存储名称
/**
* 构造函数,初始化IndexedDB连接
* @param {string} logicalStoreName 逻辑存储的名称,例如 'default', 'foo'
*/
constructor(logicalStoreName = 'default') {
this.#logicalStoreName = logicalStoreName;
// 使用固定的数据库版本号,只有当数据库首次创建或需要结构性升级时才改变
const openRequest = indexedDB.open(LocalStorageAsync.#DB_NAME, 1);
this.#database = new Promise((resolve, reject) => {
openRequest.onupgradeneeded = (event) => {
const db = event.target.result;
// 仅在onupgradeneeded中创建物理对象存储
if (!db.objectStoreNames.contains(LocalStorageAsync.#PHYSICAL_OBJECT_STORE)) {
// 使用keyPath,这里我们将组合键作为主键
db.createObjectStore(LocalStorageAsync.#PHYSICAL_OBJECT_STORE, { keyPath: 'fullKey' });
// 如果需要按逻辑存储名称查询,可以添加索引
// db.createIndex('by_logicalStoreName', 'logicalStoreName', { unique: false });
}
};
openRequest.onsuccess = (event) => {
resolve(event.target.result);
};
openRequest.onerror = (event) => {
console.error("IndexedDB open error:", event.target.errorCode, event.target.error);
reject(new Error("Failed to open IndexedDB."));
};
});
}
/**
* 获取一个事务和对象存储实例
* @param {IDBTransactionMode} mode 事务模式 ('readonly' 或 'readwrite')
* @returns {Promise<IDBObjectStore>}
*/
async #getTransactionStore(mode) {
const db = await this.#database;
const transaction = db.transaction(LocalStorageAsync.#PHYSICAL_OBJECT_STORE, mode);
transaction.onerror = (event) => {
console.error("Transaction error:", event.target.errorCode, event.target.error);
};
return transaction.objectStore(LocalStorageAsync.#PHYSICAL_OBJECT_STORE);
}
/**
* 生成用于存储的唯一键
* @param {string} key 用户提供的键
* @returns {string} 包含逻辑存储名称前缀的完整键
*/
#generateFullKey(key) {
return `${this.#logicalStoreName}__${key}`;
}
/**
* 从指定的逻辑存储中获取值
* @param {string} key 要获取的键
* @returns {Promise<string|null>} 对应的值,如果不存在则为null
*/
async getItem(key) {
const store = await this.#getTransactionStore('readonly');
const fullKey = this.#generateFullKey(key);
const request = store.get(fullKey);
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
const result = event.target.result;
resolve(result ? result.value : null);
};
request.onerror = (event) => {
console.error("Error getting item:", event.target.errorCode, event.target.error);
reject(event.target.error);
};
});
}
/**
* 将键值对存储到指定的逻辑存储中
* @param {string} key 要设置的键
* @param {string} value 要设置的值
* @returns {Promise<void>}
*/
async setItem(key, value) {
const store = await this.#getTransactionStore('readwrite');
const fullKey = this.#generateFullKey(key);
// 存储一个包含fullKey、原始值和逻辑存储名称的对象
const dataToStore = {
fullKey: fullKey,
value: value,
logicalStoreName: this.#logicalStoreName // 存储逻辑存储名称,方便未来查询或索引
};
const request = store.put(dataToStore);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = (event) => {
console.error("Error setting item:", event.target.errorCode, event.target.error);
reject(event.target.error);
};
});
}
/**
* 从指定的逻辑存储中移除键值对
* @param {string} key 要移除的键
* @returns {Promise<void>}
*/
async removeItem(key) {
const store = await this.#getTransactionStore('readwrite');
const fullKey = this.#generateFullKey(key);
const request = store.delete(fullKey);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = (event) => {
console.error("Error removing item:", event.target.errorCode, event.target.error);
reject(event.target.error);
};
});
}
/**
* 清空指定逻辑存储中的所有数据
* 注意:这会遍历并删除,而不是直接清空整个物理存储
* @returns {Promise<void>}
*/
async clear() {
const store = await this.#getTransactionStore('readwrite');
const request = store.openCursor();
const keysToDelete = [];
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
// 检查是否属于当前逻辑存储
if (cursor.value.logicalStoreName === this.#logicalStoreName) {
keysToDelete.push(cursor.primaryKey);
}
cursor.continue();
} else {
// 游标遍历完毕,开始删除
if (keysToDelete.length === 0) {
resolve();
return;
}
let deleteCount = 0;
const deleteNext = () => {
if (deleteCount < keysToDelete.length) {
const deleteRequest = store.delete(keysToDelete[deleteCount]);
deleteRequest.onsuccess = () => {
deleteCount++;
deleteNext();
};
deleteRequest.onerror = (event) => {
console.error("Error deleting item during clear:", event.target.errorCode, event.target.error);
reject(event.target.error);
};
} else {
resolve();
}
};
deleteNext();
}
};
request.onerror = (event) => {
console.error("Error opening cursor for clear:", event.target.errorCode, event.target.error);
reject(event.target.error);
};
});
}
}
// 使用示例
async function runExample() {
console.log("--- Initializing LocalStorageAsync instances ---");
const defaultStore = new LocalStorageAsync();
const fooStore = new LocalStorageAsync('foo');
const barStore = new LocalStorageAsync('bar');
console.log("\n--- Setting items ---");
await defaultStore.setItem('user', 'Alice');
await defaultStore.setItem('theme', 'dark');
await fooStore.setItem('user', 'Bob');
await fooStore.setItem('settings', 'enabled');
await barStore.setItem('user', 'Charlie');
console.log("\n--- Getting items ---");
console.log("Default store - user:", await defaultStore.getItem('user')); // Expected: Alice
console.log("Foo store - user:", await fooStore.getItem('user')); // Expected: Bob
console.log("Bar store - user:", await barStore.getItem('user')); // Expected: Charlie
console.log("Default store - theme:", await defaultStore.getItem('theme')); // Expected: dark
console.log("Foo store - settings:", await fooStore.getItem('settings')); // Expected: enabled
console.log("Default store - nonExistent:", await defaultStore.getItem('nonExistent')); // Expected: null
console.log("\n--- Removing item from fooStore ---");
await fooStore.removeItem('user');
console.log("Foo store - user after removal:", await fooStore.getItem('user')); // Expected: null
console.log("\n--- Clearing defaultStore ---");
await defaultStore.clear();
console.log("Default store - user after clear:", await defaultStore.getItem('user')); // Expected: null
console.log("Default store - theme after clear:", await defaultStore.getItem('theme')); // Expected: null
console.log("Foo store - settings (should remain):", await fooStore.getItem('settings')); // Expected: enabled
}
runExample().catch(e => console.error("Example failed:", e));通过采用这种数据层面的分区策略,你可以灵活地管理多个逻辑存储,而无需承担 IndexedDB 模式频繁变更带来的复杂性和性能开销。这种方法更加符合 IndexedDB 的设计理念,并提供了更健壮、可维护的解决方案。
以上就是IndexedDB模式管理:动态ObjectStore的替代方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号