现在web前端的开发中,客户端数据的存储与管理是不可忽视的一环。无论是临时数据缓存、表单数据保存还是简单的配置存储,选择适当的本地存储策略都至关重要。在这篇文章中,我将带大家实现一个支持三种存储策略(内存、LocalStorage和IndexedDB)的简单数据库,并分享我对于各类存储技术的拙见。
为什么我们需要本地存储?
性能优化:减少与服务器的通信,提升用户体验。
离线支持:在网络不可用的情况下,让应用依旧能够提供基本的功能。
数据持久化:存储用户个性化信息或临时数据,避免页面刷新或关闭丢失。
存储策略
内存存储:数据仅存在于页面刷新前,适合临时数据。
LocalStorage:持久化存储,数据容量较小(一般为5MB)。
IndexedDB:结构化数据存储,适合复杂数据存储,支持异步操作。
实现思路
接下来,我将带你一步步实现一个基于这三种存储策略的类,名字就叫SimpleDatabase。该类包含一系列用于数据存储、读取、删除、查询的同步和异步方法。
初始化构造函数
构造函数用于初始化数据库,接收一个存储类型作为参数,默认值为memory(也就是内存)。如果选择indexedDB存储,则需要对此数据库进行初始化。
class SimpleDatabase {
private storageType: StorageType;
private memoryStorage: Record<string, any> = {};
private localStorageKey = 'SimpleDatabase';
private dbName = 'SimpleDatabase';
private storeName = 'store';
constructor(storageType: StorageType = 'memory') {
this.storageType = storageType;
if (storageType === 'indexedDB') {
this.initIndexedDB();
}
}
private initIndexedDB() {
const request = indexedDB.open(this.dbName, 1);
request.onerror = (event) => {
console.error('Database error:', event.target);
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, { keyPath: 'id' });
}
};
}
// ...
}
异步与同步操作
对于每个操作(保存、获取、删除),我们都需要既实现异步方法,也实现同步方法。异步方法对于indexedDB操作尤为重要,因为这类操作本身就是异步的,本来是打算都做成同步执行的,但是indexedDB同步执行的话,性能影响比较严重,所以就同步异步都写一下。
保存数据
1.异步保存数据
public async saveAsync(key: string, value: any): Promise<void> {
try {
if (this.storageType === 'localStorage') {
const data = this.retrieveAllSync() || {};
data[key] = value;
localStorage.setItem(this.localStorageKey, JSON.stringify(data));
} else if (this.storageType === 'indexedDB') {
const db = await this.getIDBDatabase();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await this.requestAsPromise(store.put({ id: key, value }));
} else {
this.memoryStorage[key] = value;
}
} catch (error) {
console.error('Error saving data:', error);
}
}
2.同步保存数据
public saveSync(key: string, value: any): void {
if (this.storageType === 'localStorage') {
const data = this.retrieveAllSync() || {};
data[key] = value;
localStorage.setItem(this.localStorageKey, JSON.stringify(data));
} else {
this.memoryStorage[key] = value;
}
}
获取数据
1.异步获取数据
public async retrieveAsync(key: string): Promise<any> {
try {
if (this.storageType === 'localStorage') {
const data = this.retrieveAllSync();
return data ? data[key] : null;
} else if (this.storageType === 'indexedDB') {
const db = await this.getIDBDatabase();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const result = await this.requestAsPromise(store.get(key));
return result?.value ?? null;
} else {
return this.memoryStorage[key] || null;
}
} catch (error) {
console.error('Error retrieving data:', error);
return null;
}
}
2.同步获取数据
public retrieveSync(key: string): any {
if (this.storageType === 'localStorage') {
const data = this.retrieveAllSync();
return data ? data[key] : null;
} else {
return this.memoryStorage[key] || null;
}
}
批量操作与查询
支持批量保存数据和基于自定义过滤函数的查询操作。
批量保存数据
为了提高性能,我们可以批量保存数据。从数据结构上看,一种常见的做法是接收一个键值对数组,并使用forEach或者for...of循环进行保存。
// 批量异步保存
public async saveAllAsync(keyValuePairs: [string, any][]): Promise<void> {
for (const [key, value] of keyValuePairs) {
await this.saveAsync(key, value);
}
}
// 批量同步保存
public saveAllSync(keyValuePairs: [string, any][]): void {
keyValuePairs.forEach(([key, value]) => this.saveSync(key, value));
}
查询数据
再实现一个简单的查询方法,该方法接收一个过滤函数,并返回满足条件的数据。
// 异步查询
public async findAsync(filterFn: (value: any) => boolean): Promise<any[]> {
const allData = await this.retrieveAllAsync();
return Object.values(allData).filter(filterFn);
}
// 同步查询
public findSync(filterFn: (value: any) => boolean): any[] {
const allData = this.retrieveAllSync();
return Object.values(allData).filter(filterFn);
}
错误处理
为了确保代码的健壮性,我们在每个异步方法中都加入了基础的错误处理。大家可以根据自己相关业务需求进一步完善错误处理逻辑,这个方法再上述代码中有多处都使用了,大家可以回到上面看看。
private requestAsPromise<T>(request: IDBRequest): Promise<T> {
return new Promise((resolve, reject) => {
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
三种存储策略优劣分析
| 优点 | 缺点 | |
|---|---|---|
| 内存存储 | 速度快,实现简单 | 数据不持久化,页面刷新数据丢失 |
| LocalStorage | 简单易用,页面刷新数据保留 | 存储大小有限(一般为5M),不适合存储复杂的数据结构 |
| IndexedDB | 支持大容量数据存储(通常可达100+M);结构化存储,支持事务及索引,异步操作,离线存储 | 学习成本高,不支持跨域,兼容性问题,使用繁琐,不同浏览器对IndexedDB的支持程度和具体实现可能存在差异 |
总结
本文通过实现一个SimpleDatabase类,展示了如何在我们的Web应用中实现基于内存、LocalStorage和IndexedDB的多种存储策略。每种存储方式都有自己的优缺点,选择适当的存储方式需要根据具体的业务需求和平衡点来决定。
在现代Web前端开发中,数据存储管理是不可忽视的重要环节。通过对比和实现不同的存储策略,我们不仅能更好地理解这些存储技术的特性与限制,也能为实际项目选择最合适的解决方案提供借鉴。
当然还有其他的存储方式,大家可以自行去了解一下,我就不多赘述了。