浏览器存储 API:全面解析与高级实践
引言
在现代Web开发中,浏览器存储是构建丰富、离线可用、高性能应用的关键技术。从简单的键值对存储到复杂的数据库操作,浏览器提供了多种存储方案。本文将深入探讨各种浏览器存储API,包括它们的特性、使用场景、性能优化和最佳实践。
一、本地存储的现代封装
1.1 增强型 localStorage/sessionStorage 封装
javascript
class StorageManager {
constructor(storageType = 'localStorage') {
this.storage = window[storageType];
this.prefix = 'app_';
this.encryptionEnabled = false;
this.compressionEnabled = false;
this.observers = new Map();
this.expiryTimes = new Map();
this.cleanupInterval = null;
}
// 初始化存储管理器
init(options = {}) {
const {
prefix = 'app_',
encryptionKey = null,
compressionThreshold = 1024, // 1KB
cleanupInterval = 60000 // 1分钟
} = options;
this.prefix = prefix;
if (encryptionKey) {
this.enableEncryption(encryptionKey);
}
if (compressionThreshold > 0) {
this.compressionEnabled = true;
this.compressionThreshold = compressionThreshold;
}
// 启动过期数据清理
this.startCleanupInterval(cleanupInterval);
// 监听storage事件实现跨标签页同步
window.addEventListener('storage', this.handleStorageEvent.bind(this));
return this;
}
// 生成带前缀的键名
getPrefixedKey(key) {
return `${this.prefix}${key}`;
}
// 序列化数据
serialize(data) {
const serialized = JSON.stringify({
data,
timestamp: Date.now(),
version: '1.0'
});
// 压缩大文本数据
if (this.compressionEnabled && serialized.length > this.compressionThreshold) {
return this.compress(serialized);
}
return serialized;
}
// 反序列化数据
deserialize(serialized) {
try {
let data = serialized;
// 解压缩(如果有压缩)
if (this.isCompressed(serialized)) {
data = this.decompress(serialized);
}
const parsed = JSON.parse(data);
// 验证数据完整性
if (!parsed.data || !parsed.timestamp) {
throw new Error('Invalid data format');
}
return parsed.data;
} catch (error) {
console.warn('Failed to deserialize data:', error);
return null;
}
}
// 数据压缩
compress(text) {
try {
// 简单的压缩算法,实际项目中可使用pako等库
const base64 = btoa(encodeURIComponent(text));
return `compressed:${base64}`;
} catch {
return text;
}
}
// 数据解压缩
decompress(text) {
if (!text.startsWith('compressed:')) return text;
try {
const base64 = text.slice(11);
return decodeURIComponent(atob(base64));
} catch {
return text;
}
}
// 判断是否压缩
isCompressed(text) {
return text.startsWith('compressed:');
}
// 设置数据
set(key, value, options = {}) {
const {
ttl = null, // 过期时间(毫秒)
encrypt = false
} = options;
try {
const prefixedKey = this.getPrefixedKey(key);
let processedValue = value;
// 加密数据
if (encrypt && this.encryptionEnabled) {
processedValue = this.encryptData(value);
}
const serialized = this.serialize(processedValue);
this.storage.setItem(prefixedKey, serialized);
// 设置过期时间
if (ttl) {
const expiryTime = Date.now() + ttl;
this.expiryTimes.set(prefixedKey, expiryTime);
}
// 通知观察者
this.notifyObservers(key, value, 'set');
return true;
} catch (error) {
console.error('Failed to set storage item:', error);
return false;
}
}
// 获取数据
get(key, defaultValue = null) {
try {
const prefixedKey = this.getPrefixedKey(key);
const serialized = this.storage.getItem(prefixedKey);
if (serialized === null) {
return defaultValue;
}
// 检查是否过期
if (this.isExpired(prefixedKey)) {
this.remove(key);
return defaultValue;
}
let data = this.deserialize(serialized);
// 解密数据
if (typeof data === 'string' && data.startsWith('encrypted:')) {
data = this.decryptData(data);
}
return data;
} catch (error) {
console.error('Failed to get storage item:', error);
return defaultValue;
}
}
// 删除数据
remove(key) {
const prefixedKey = this.getPrefixedKey(key);
this.storage.removeItem(prefixedKey);
this.expiryTimes.delete(prefixedKey);
this.notifyObservers(key, null, 'remove');
return true;
}
// 清空所有数据(保留前缀)
clear() {
const keysToRemove = [];
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key.startsWith(this.prefix)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => {
this.storage.removeItem(key);
this.expiryTimes.delete(key);
});
this.notifyObservers('*', null, 'clear');
return true;
}
// 检查数据是否过期
isExpired(key) {
const expiryTime = this.expiryTimes.get(key);
if (!expiryTime) return false;
return Date.now() > expiryTime;
}
// 启用加密
enableEncryption(encryptionKey) {
if (typeof window.crypto === 'undefined') {
console.warn('Web Crypto API not available, encryption disabled');
return false;
}
this.encryptionEnabled = true;
this.encryptionKey = encryptionKey;
return true;
}
// 加密数据
async encryptData(data) {
try {
const textEncoder = new TextEncoder();
const dataBuffer = textEncoder.encode(JSON.stringify(data));
// 生成随机的初始化向量
const iv = window.crypto.getRandomValues(new Uint8Array(12));
// 导入密钥
const keyMaterial = await window.crypto.subtle.importKey(
'raw',
textEncoder.encode(this.encryptionKey),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
// 派生密钥
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: textEncoder.encode('storage-salt'),
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
// 加密数据
const encryptedBuffer = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
dataBuffer
);
// 组合IV和加密数据
const combinedBuffer = new Uint8Array(iv.length + encryptedBuffer.byteLength);
combinedBuffer.set(iv, 0);
combinedBuffer.set(new Uint8Array(encryptedBuffer), iv.length);
// 转换为base64字符串
const base64String = btoa(String.fromCharCode.apply(null, combinedBuffer));
return `encrypted:${base64String}`;
} catch (error) {
console.error('Encryption failed:', error);
return data;
}
}
// 解密数据
async decryptData(encryptedString) {
try {
if (!encryptedString.startsWith('encrypted:')) {
return encryptedString;
}
const base64String = encryptedString.slice(10);
const combinedBuffer = Uint8Array.from(atob(base64String), c => c.charCodeAt(0));
// 提取IV和加密数据
const iv = combinedBuffer.slice(0, 12);
const encryptedBuffer = combinedBuffer.slice(12);
const textEncoder = new TextEncoder();
const keyMaterial = await window.crypto.subtle.importKey(
'raw',
textEncoder.encode(this.encryptionKey),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: textEncoder.encode('storage-salt'),
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
// 解密数据
const decryptedBuffer = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
encryptedBuffer
);
const textDecoder = new TextDecoder();
const decryptedString = textDecoder.decode(decryptedBuffer);
return JSON.parse(decryptedString);
} catch (error) {
console.error('Decryption failed:', error);
return encryptedString;
}
}
// 添加观察者
observe(key, callback) {
if (!this.observers.has(key)) {
this.observers.set(key, new Set());
}
this.observers.get(key).add(callback);
// 返回取消观察的函数
return () => {
const observers = this.observers.get(key);
if (observers) {
observers.delete(callback);
if (observers.size === 0) {
this.observers.delete(key);
}
}
};
}
// 通知观察者
notifyObservers(key, value, action) {
// 通知特定键的观察者
const keyObservers = this.observers.get(key);
if (keyObservers) {
keyObservers.forEach(callback => {
callback(value, action, key);
});
}
// 通知全局观察者
const globalObservers = this.observers.get('*');
if (globalObservers) {
globalObservers.forEach(callback => {
callback(value, action, key);
});
}
}
// 处理storage事件
handleStorageEvent(event) {
if (event.storageArea !== this.storage) return;
const key = event.key;
if (key && key.startsWith(this.prefix)) {
const unprefixedKey = key.slice(this.prefix.length);
const value = event.newValue ? this.get(unprefixedKey) : null;
const action = event.newValue ? 'set' : 'remove';
this.notifyObservers(unprefixedKey, value, action);
}
}
// 启动清理定时器
startCleanupInterval(interval) {
this.cleanupInterval = setInterval(() => {
this.cleanupExpired();
}, interval);
}
// 清理过期数据
cleanupExpired() {
const now = Date.now();
const expiredKeys = [];
this.expiryTimes.forEach((expiryTime, key) => {
if (now > expiryTime) {
expiredKeys.push(key);
}
});
expiredKeys.forEach(key => {
const unprefixedKey = key.slice(this.prefix.length);
this.remove(unprefixedKey);
});
if (expiredKeys.length > 0) {
console.log(`Cleaned up ${expiredKeys.length} expired items`);
}
}
// 获取存储统计信息
getStats() {
let totalSize = 0;
let itemCount = 0;
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key.startsWith(this.prefix)) {
const value = this.storage.getItem(key);
totalSize += (key.length + value.length) * 2; // UTF-16字符占2字节
itemCount++;
}
}
return {
totalSize,
itemCount,
estimatedRemaining: 5 * 1024 * 1024 - totalSize, // 假设5MB限制
usagePercentage: (totalSize / (5 * 1024 * 1024)) * 100
};
}
// 批量操作
batch(operations) {
return operations.reduce((results, operation) => {
const { type, key, value, options } = operation;
let result;
switch (type) {
case 'set':
result = this.set(key, value, options);
break;
case 'get':
result = this.get(key);
break;
case 'remove':
result = this.remove(key);
break;
default:
result = null;
}
results.push({ key, result });
return results;
}, []);
}
// 获取所有键
keys() {
const keys = [];
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key.startsWith(this.prefix)) {
keys.push(key.slice(this.prefix.length));
}
}
return keys;
}
// 导出所有数据
export() {
const data = {};
for (const key of this.keys()) {
data[key] = this.get(key);
}
return {
timestamp: Date.now(),
version: '1.0',
prefix: this.prefix,
data
};
}
// 导入数据
import(data) {
if (!data || !data.data) {
throw new Error('Invalid import data');
}
Object.entries(data.data).forEach(([key, value]) => {
this.set(key, value);
});
return true;
}
// 销毁实例
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
window.removeEventListener('storage', this.handleStorageEvent);
this.observers.clear();
this.expiryTimes.clear();
}
}
// 使用示例
class StorageExample {
static async demonstrate() {
// 创建存储管理器
const storage = new StorageManager('localStorage');
// 初始化
storage.init({
prefix: 'myapp_',
encryptionKey: 'my-secret-key-123',
compressionThreshold: 500,
cleanupInterval: 30000
});
// 设置带过期时间的数据
storage.set('user', {
id: 123,
name: 'John Doe',
email: 'john@example.com'
}, { ttl: 3600000 }); // 1小时后过期
// 设置加密数据
storage.set('token', 'secret-token-abc', { encrypt: true });
// 获取数据
const user = storage.get('user');
const token = storage.get('token');
console.log('User:', user);
console.log('Token:', token);
// 观察数据变化
const unsubscribe = storage.observe('user', (value, action) => {
console.log(`User data ${action}:`, value);
});
// 批量操作
const results = storage.batch([
{ type: 'set', key: 'setting1', value: 'value1' },
{ type: 'set', key: 'setting2', value: 'value2' },
{ type: 'get', key: 'setting1' },
{ type: 'remove', key: 'setting2' }
]);
console.log('Batch results:', results);
// 获取统计信息
const stats = storage.getStats();
console.log('Storage stats:', stats);
// 导出数据
const exported = storage.export();
console.log('Exported data:', exported);
// 取消观察
unsubscribe();
return storage;
}
}
1.2 存储类型自动选择器
javascript
class SmartStorage {
constructor(options = {}) {
this.options = {
memoryLimit: 100, // 内存缓存项数限制
fallbackOrder: ['memory', 'localStorage', 'sessionStorage', 'cookie'],
defaultTTL: 24 * 60 * 60 * 1000, // 默认24小时过期
...options
};
this.storages = {
memory: new Map(),
localStorage: window.localStorage,
sessionStorage: window.sessionStorage,
cookie: this.createCookieHandler()
};
this.stats = {
hits: { memory: 0, localStorage: 0, sessionStorage: 0, cookie: 0 },
misses: 0,
writes: 0,
deletes: 0
};
this.memoryQueue = [];
this.init();
}
createCookieHandler() {
return {
setItem: (key, value, days = 7) => {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
},
getItem: (key) => {
const match = document.cookie.match(new RegExp('(^| )' + key + '=([^;]+)'));
return match ? decodeURIComponent(match[2]) : null;
},
removeItem: (key) => {
document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
}
};
}
init() {
// 尝试检测存储可用性
this.storageAvailable = this.detectStorageAvailability();
// 设置内存缓存清理定时器
setInterval(() => this.cleanupMemoryCache(), 60000);
}
detectStorageAvailability() {
const results = {};
try {
const testKey = '__storage_test__';
results.localStorage = (() => {
try {
window.localStorage.setItem(testKey, testKey);
window.localStorage.removeItem(testKey);
return true;
} catch (e) {
return false;
}
})();
results.sessionStorage = (() => {
try {
window.sessionStorage.setItem(testKey, testKey);
window.sessionStorage.removeItem(testKey);
return true;
} catch (e) {
return false;
}
})();
results.cookie = navigator.cookieEnabled;
return results;
} catch (e) {
return {
localStorage: false,
sessionStorage: false,
cookie: false
};
}
}
// 智能设置数据
async set(key, value, options = {}) {
const {
ttl = this.options.defaultTTL,
priority = 'normal', // 'low', 'normal', 'high', 'critical'
persist = true,
compress = false
} = options;
const storageMeta = {
timestamp: Date.now(),
expiry: Date.now() + ttl,
priority,
persist,
compress,
value
};
// 总是更新内存缓存
this.updateMemoryCache(key, storageMeta);
// 根据优先级和持久化选项选择存储后端
if (persist) {
await this.persistToStorage(key, storageMeta);
}
this.stats.writes++;
return true;
}
updateMemoryCache(key, meta) {
// 检查内存限制
if (this.memoryQueue.length >= this.options.memoryLimit) {
const oldestKey = this.memoryQueue.shift();
this.storages.memory.delete(oldestKey);
}
this.storages.memory.set(key, meta);
// 更新队列顺序
const existingIndex = this.memoryQueue.indexOf(key);
if (existingIndex > -1) {
this.memoryQueue.splice(existingIndex, 1);
}
this.memoryQueue.push(key);
}
async persistToStorage(key, meta) {
const storageValue = JSON.stringify({
data: meta.value,
meta: {
timestamp: meta.timestamp,
expiry: meta.expiry,
priority: meta.priority,
compress: meta.compress
}
});
// 根据优先级选择存储后端
let storageBackend = 'localStorage';
if (meta.priority === 'critical') {
// 关键数据存储到多个后端
if (this.storageAvailable.localStorage) {
try {
this.storages.localStorage.setItem(key, storageValue);
} catch (e) {
console.warn('localStorage full, trying sessionStorage');
}
}
if (this.storageAvailable.sessionStorage) {
try {
this.storages.sessionStorage.setItem(key, storageValue);
} catch (e) {
console.warn('sessionStorage full, trying cookies');
}
}
if (this.storageAvailable.cookie) {
this.storages.cookie.setItem(key, storageValue, 7); // 7天
}
} else if (meta.priority === 'high') {
storageBackend = this.storageAvailable.localStorage ? 'localStorage' :
this.storageAvailable.sessionStorage ? 'sessionStorage' : 'cookie';
} else {
storageBackend = this.options.fallbackOrder.find(backend =>
this.storageAvailable[backend] !== false
) || 'memory';
}
try {
if (storageBackend === 'cookie') {
this.storages.cookie.setItem(key, storageValue, Math.ceil((meta.expiry - Date.now()) / 86400000));
} else if (storageBackend === 'localStorage' || storageBackend === 'sessionStorage') {
this.storages[storageBackend].setItem(key, storageValue);
}
} catch (error) {
console.warn(`Failed to persist to ${storageBackend}:`, error);
// 尝试下一个后备存储
const nextBackend = this.options.fallbackOrder[
this.options.fallbackOrder.indexOf(storageBackend) + 1
];
if (nextBackend) {
await this.persistToStorage(key, { ...meta, storageBackend: nextBackend });
}
}
}
// 智能获取数据
async get(key, options = {}) {
const { forceFresh = false } = options;
// 首先检查内存缓存
if (!forceFresh && this.storages.memory.has(key)) {
const cached = this.storages.memory.get(key);
// 检查是否过期
if (Date.now() > cached.expiry) {
this.storages.memory.delete(key);
this.stats.misses++;
} else {
// 更新访问时间
cached.lastAccessed = Date.now();
this.stats.hits.memory++;
// 移动到最后(LRU)
const index = this.memoryQueue.indexOf(key);
if (index > -1) {
this.memoryQueue.splice(index, 1);
this.memoryQueue.push(key);
}
return cached.value;
}
}
// 从持久化存储中查找
let result = null;
for (const backend of this.options.fallbackOrder) {
if (backend === 'memory') continue;
try {
let storedValue = null;
if (backend === 'cookie') {
storedValue = this.storages.cookie.getItem(key);
} else if (backend === 'localStorage' || backend === 'sessionStorage') {
storedValue = this.storages[backend].getItem(key);
}
if (storedValue) {
const parsed = JSON.parse(storedValue);
// 检查是否过期
if (Date.now() > parsed.meta.expiry) {
this.remove(key); // 删除过期数据
continue;
}
result = parsed.data;
this.stats.hits[backend]++;
// 更新到内存缓存
this.updateMemoryCache(key, {
value: result,
timestamp: parsed.meta.timestamp,
expiry: parsed.meta.expiry,
priority: parsed.meta.priority,
persist: true,
compress: parsed.meta.compress
});
break;
}
} catch (error) {
console.warn(`Error reading from ${backend}:`, error);
continue;
}
}
if (result === null) {
this.stats.misses++;
}
return result;
}
// 删除数据
async remove(key) {
// 从所有存储后端删除
this.storages.memory.delete(key);
const index = this.memoryQueue.indexOf(key);
if (index > -1) {
this.memoryQueue.splice(index, 1);
}
if (this.storageAvailable.localStorage) {
this.storages.localStorage.removeItem(key);
}
if (this.storageAvailable.sessionStorage) {
this.storages.sessionStorage.removeItem(key);
}
if (this.storageAvailable.cookie) {
this.storages.cookie.removeItem(key);
}
this.stats.deletes++;
return true;
}
// 清理内存缓存
cleanupMemoryCache() {
const now = Date.now();
let cleaned = 0;
for (const [key, meta] of this.storages.memory.entries()) {
if (now > meta.expiry) {
this.storages.memory.delete(key);
cleaned++;
}
}
// 如果仍然超过限制,移除最旧的项
if (this.memoryQueue.length > this.options.memoryLimit) {
const toRemove = this.memoryQueue.slice(0, this.memoryQueue.length - this.options.memoryLimit);
toRemove.forEach(key => {
this.storages.memory.delete(key);
});
this.memoryQueue = this.memoryQueue.slice(-this.options.memoryLimit);
cleaned += toRemove.length;
}
if (cleaned > 0) {
console.log(`Cleaned ${cleaned} items from memory cache`);
}
}
// 获取缓存统计信息
getStats() {
const now = Date.now();
const memoryItems = Array.from(this.storages.memory.entries());
return {
...this.stats,
memory: {
items: memoryItems.length,
hits: this.stats.hits.memory,
oldest: memoryItems.length > 0 ?
Math.round((now - memoryItems[0][1].timestamp) / 1000) : 0,
newest: memoryItems.length > 0 ?
Math.round((now - memoryItems[memoryItems.length - 1][1].timestamp) / 1000) : 0
},
localStorage: {
available: this.storageAvailable.localStorage,
hits: this.stats.hits.localStorage
},
sessionStorage: {
available: this.storageAvailable.sessionStorage,
hits: this.stats.hits.sessionStorage
},
cookie: {
available: this.storageAvailable.cookie,
hits: this.stats.hits.cookie
}
};
}
// 清空所有存储
async clear() {
this.storages.memory.clear();
this.memoryQueue = [];
if (this.storageAvailable.localStorage) {
this.storages.localStorage.clear();
}
if (this.storageAvailable.sessionStorage) {
this.storages.sessionStorage.clear();
}
// 清除cookie需要特殊处理
if (this.storageAvailable.cookie) {
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const eqPos = cookie.indexOf('=');
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
}
}
return true;
}
}
二、IndexedDB 高级封装
2.1 完整的 IndexedDB 包装器
javascript
class IndexedDBManager {
constructor(dbName, version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
this.stores = new Map();
this.transactions = new Map();
this.queue = [];
this.processingQueue = false;
this.autoIncrementCounters = new Map();
this.migrationRegistry = new Map();
// 注册事件
this.events = {
onUpgradeNeeded: new Set(),
onSuccess: new Set(),
onError: new Set(),
onVersionChange: new Set()
};
}
// 注册数据库迁移
registerMigration(fromVersion, toVersion, migration) {
const key = `${fromVersion}_${toVersion}`;
this.migrationRegistry.set(key, migration);
}
// 初始化数据库
async init(schema = {}) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = (event) => {
console.error('IndexedDB opening error:', event.target.error);
this.events.onError.forEach(callback => callback(event.target.error));
reject(event.target.error);
};
request.onsuccess = (event) => {
this.db = event.target.result;
console.log(`IndexedDB ${this.dbName} opened successfully`);
// 监听版本变化
this.db.onversionchange = (event) => {
console.log('Database version changed, closing connections');
this.events.onVersionChange.forEach(callback => callback(event));
this.db.close();
};
this.events.onSuccess.forEach(callback => callback(this.db));
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
const newVersion = event.newVersion;
console.log(`Upgrading database from ${oldVersion} to ${newVersion}`);
// 执行注册的迁移
this.executeMigrations(db, oldVersion, newVersion);
// 应用新模式
this.applySchema(db, schema);
this.events.onUpgradeNeeded.forEach(callback => callback(event));
};
});
}
// 执行迁移
executeMigrations(db, oldVersion, newVersion) {
for (let version = oldVersion; version < newVersion; version++) {
const migrationKey = `${version}_${version + 1}`;
const migration = this.migrationRegistry.get(migrationKey);
if (migration) {
console.log(`Executing migration ${migrationKey}`);
try {
migration(db, version, version + 1);
} catch (error) {
console.error(`Migration ${migrationKey} failed:`, error);
throw error;
}
}
}
}
// 应用数据库模式
applySchema(db, schema) {
Object.entries(schema).forEach(([storeName, storeConfig]) => {
if (!db.objectStoreNames.contains(storeName)) {
console.log(`Creating object store: ${storeName}`);
const { keyPath = 'id', autoIncrement = true, indices = [] } = storeConfig;
const store = db.createObjectStore(storeName, { keyPath, autoIncrement });
// 创建索引
indices.forEach(indexConfig => {
const { name, keyPath, unique = false, multiEntry = false } = indexConfig;
store.createIndex(name, keyPath, { unique, multiEntry });
});
this.stores.set(storeName, { ...storeConfig, store });
} else {
// 更新现有存储
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const existingIndices = Array.from(store.indexNames);
const { indices = [] } = storeConfig;
// 添加新索引
indices.forEach(indexConfig => {
if (!existingIndices.includes(indexConfig.name)) {
console.log(`Creating index ${indexConfig.name} on ${storeName}`);
store.createIndex(
indexConfig.name,
indexConfig.keyPath,
{ unique: indexConfig.unique || false, multiEntry: indexConfig.multiEntry || false }
);
}
});
}
});
}
// 添加数据
async add(storeName, data) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
// 生成ID(如果需要)
let processedData = { ...data };
const storeConfig = this.stores.get(storeName);
if (storeConfig.autoIncrement && !processedData[storeConfig.keyPath]) {
const counter = this.getAutoIncrementCounter(storeName);
processedData[storeConfig.keyPath] = counter;
}
const request = store.add(processedData);
request.onsuccess = (event) => {
resolve({ id: event.target.result, data: processedData });
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 批量添加数据
async addBulk(storeName, items) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const results = [];
items.forEach((item, index) => {
let processedItem = { ...item };
const storeConfig = this.stores.get(storeName);
if (storeConfig.autoIncrement && !processedItem[storeConfig.keyPath]) {
const counter = this.getAutoIncrementCounter(storeName, index);
processedItem[storeConfig.keyPath] = counter;
}
const request = store.add(processedItem);
request.onsuccess = (event) => {
results.push({ id: event.target.result, data: processedItem });
if (results.length === items.length) {
resolve(results);
}
};
request.onerror = (event) => {
reject(event.target.error);
};
});
});
}
// 获取数据
async get(storeName, key) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const request = store.get(key);
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 通过索引查询
async getByIndex(storeName, indexName, value) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const request = index.get(value);
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 查询所有数据
async getAll(storeName, query = null, count = null) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
let request;
if (query) {
const range = this.createRange(query);
request = store.getAll(range, count);
} else {
request = store.getAll();
}
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 创建IDBKeyRange
createRange(query) {
if (typeof query === 'object') {
if (query.lower && query.upper) {
return IDBKeyRange.bound(query.lower, query.upper, query.lowerOpen, query.upperOpen);
} else if (query.lower) {
return IDBKeyRange.lowerBound(query.lower, query.open);
} else if (query.upper) {
return IDBKeyRange.upperBound(query.upper, query.open);
} else if (query.only) {
return IDBKeyRange.only(query.only);
}
}
return IDBKeyRange.only(query);
}
// 更新数据
async update(storeName, key, updates) {
return new Promise(async (resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
try {
// 先获取现有数据
const existing = await this.get(storeName, key);
if (!existing) {
reject(new Error(`Record with key ${key} not found`));
return;
}
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const updatedData = { ...existing, ...updates, _updatedAt: Date.now() };
const request = store.put(updatedData);
request.onsuccess = (event) => {
resolve(updatedData);
};
request.onerror = (event) => {
reject(event.target.error);
};
} catch (error) {
reject(error);
}
});
}
// 删除数据
async delete(storeName, key) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.delete(key);
request.onsuccess = (event) => {
resolve(true);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 清空存储
async clearStore(storeName) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.clear();
request.onsuccess = (event) => {
resolve(true);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 计数
async count(storeName, query = null) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
let request;
if (query) {
const range = this.createRange(query);
request = store.count(range);
} else {
request = store.count();
}
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 游标遍历
async cursor(storeName, callback, options = {}) {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const { direction = 'next', range = null, indexName = null } = options;
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
let source = store;
if (indexName) {
source = store.index(indexName);
}
const rangeObj = range ? this.createRange(range) : null;
const request = source.openCursor(rangeObj, direction);
const results = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const shouldContinue = callback(cursor.value, cursor);
results.push(cursor.value);
if (shouldContinue !== false) {
cursor.continue();
} else {
resolve(results);
}
} else {
resolve(results);
}
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 批量操作事务
async transaction(operations) {
return new Promise(async (resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
// 获取所有涉及的存储名称
const storeNames = [...new Set(operations.map(op => op.store))];
const transaction = this.db.transaction(storeNames, 'readwrite');
const results = [];
let errorOccurred = false;
transaction.onerror = (event) => {
errorOccurred = true;
reject(event.target.error);
};
transaction.oncomplete = (event) => {
if (!errorOccurred) {
resolve(results);
}
};
for (const operation of operations) {
try {
const { store, type, data, key, updates } = operation;
const objectStore = transaction.objectStore(store);
let request;
switch (type) {
case 'add':
request = objectStore.add(data);
break;
case 'put':
request = objectStore.put(data);
break;
case 'get':
request = objectStore.get(key);
break;
case 'delete':
request = objectStore.delete(key);
break;
case 'update':
// 对于更新,需要先获取再更新
const getRequest = objectStore.get(key);
getRequest.onsuccess = (event) => {
const existing = event.target.result;
if (existing) {
const updated = { ...existing, ...updates };
objectStore.put(updated);
}
};
continue;
default:
throw new Error(`Unknown operation type: ${type}`);
}
request.onsuccess = (event) => {
results.push({
store,
type,
result: event.target.result,
success: true
});
};
request.onerror = (event) => {
results.push({
store,
type,
error: event.target.error,
success: false
});
};
} catch (error) {
results.push({
store: operation.store,
type: operation.type,
error,
success: false
});
}
}
});
}
// 获取自增计数器
getAutoIncrementCounter(storeName, offset = 0) {
if (!this.autoIncrementCounters.has(storeName)) {
this.autoIncrementCounters.set(storeName, 1);
}
const current = this.autoIncrementCounters.get(storeName);
this.autoIncrementCounters.set(storeName, current + 1 + offset);
return current + offset;
}
// 数据库备份
async backup() {
if (!this.db) {
throw new Error('Database not initialized');
}
const backup = {
dbName: this.dbName,
version: this.version,
timestamp: Date.now(),
stores: {}
};
const storeNames = Array.from(this.db.objectStoreNames);
for (const storeName of storeNames) {
const data = await this.getAll(storeName);
backup.stores[storeName] = data;
}
return backup;
}
// 从备份恢复
async restore(backup) {
if (!this.db) {
throw new Error('Database not initialized');
}
// 清空现有数据
const storeNames = Array.from(this.db.objectStoreNames);
for (const storeName of storeNames) {
await this.clearStore(storeName);
}
// 恢复数据
for (const [storeName, data] of Object.entries(backup.stores)) {
if (this.db.objectStoreNames.contains(storeName)) {
await this.addBulk(storeName, data);
}
}
return true;
}
// 获取数据库信息
async getInfo() {
if (!this.db) {
throw new Error('Database not initialized');
}
const info = {
name: this.db.name,
version: this.db.version,
objectStores: [],
estimatedSize: 0
};
const storeNames = Array.from(this.db.objectStoreNames);
for (const storeName of storeNames) {
const count = await this.count(storeName);
info.objectStores.push({
name: storeName,
count
});
}
// 尝试估算大小(通过读取所有数据)
try {
let totalSize = 0;
for (const storeName of storeNames) {
const allData = await this.getAll(storeName);
const jsonString = JSON.stringify(allData);
totalSize += new Blob([jsonString]).size;
}
info.estimatedSize = totalSize;
} catch (error) {
console.warn('Could not estimate database size:', error);
}
return info;
}
// 关闭数据库
close() {
if (this.db) {
this.db.close();
this.db = null;
console.log(`Database ${this.dbName} closed`);
}
}
// 删除数据库
static async deleteDatabase(dbName) {
return new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(dbName);
request.onsuccess = () => {
console.log(`Database ${dbName} deleted`);
resolve(true);
};
request.onerror = (event) => {
reject(event.target.error);
};
request.onblocked = () => {
console.warn(`Database ${dbName} deletion blocked`);
reject(new Error('Database deletion blocked by open connections'));
};
});
}
}
// 使用示例
class IndexedDBExample {
static async demonstrate() {
// 创建数据库管理器
const dbManager = new IndexedDBManager('MyAppDB', 2);
// 注册迁移
dbManager.registerMigration(1, 2, (db, oldVersion, newVersion) => {
// 从版本1迁移到版本2
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
store.createIndex('email', 'email', { unique: true });
}
});
// 定义数据库模式
const schema = {
users: {
keyPath: 'id',
autoIncrement: true,
indices: [
{ name: 'email', keyPath: 'email', unique: true },
{ name: 'name', keyPath: 'name', unique: false }
]
},
products: {
keyPath: 'sku',
autoIncrement: false,
indices: [
{ name: 'category', keyPath: 'category', unique: false },
{ name: 'price', keyPath: 'price', unique: false }
]
}
};
// 初始化数据库
await dbManager.init(schema);
// 添加用户
const user = await dbManager.add('users', {
name: 'John Doe',
email: 'john@example.com',
age: 30
});
console.log('Added user:', user);
// 批量添加产品
const products = await dbManager.addBulk('products', [
{ sku: 'P001', name: 'Product 1', category: 'electronics', price: 99.99 },
{ sku: 'P002', name: 'Product 2', category: 'clothing', price: 49.99 },
{ sku: 'P003', name: 'Product 3', category: 'electronics', price: 199.99 }
]);
console.log('Added products:', products);
// 通过索引查询
const userByEmail = await dbManager.getByIndex('users', 'email', 'john@example.com');
console.log('User by email:', userByEmail);
// 游标遍历
const expensiveProducts = await dbManager.cursor('products', (product, cursor) => {
console.log('Product:', product);
// 返回false停止遍历
return product.price < 150;
}, { indexName: 'price', direction: 'next' });
// 获取数据库信息
const info = await dbManager.getInfo();
console.log('Database info:', info);
// 备份数据库
const backup = await dbManager.backup();
console.log('Backup created:', backup);
return dbManager;
}
}
2.2 IndexedDB 查询构建器
javascript
class IndexedDBQueryBuilder {
constructor(dbManager, storeName) {
this.dbManager = dbManager;
this.storeName = storeName;
this.query = {
filters: [],
sort: null,
limit: null,
offset: 0,
indexes: []
};
}
// 等于条件
where(field, value) {
this.query.filters.push({ field, operator: '=', value });
return this;
}
// 不等于条件
whereNot(field, value) {
this.query.filters.push({ field, operator: '!=', value });
return this;
}
// 大于条件
whereGreaterThan(field, value) {
this.query.filters.push({ field, operator: '>', value });
return this;
}
// 小于条件
whereLessThan(field, value) {
this.query.filters.push({ field, operator: '<', value });
return this;
}
// 大于等于条件
whereGreaterOrEqual(field, value) {
this.query.filters.push({ field, operator: '>=', value });
return this;
}
// 小于等于条件
whereLessOrEqual(field, value) {
this.query.filters.push({ field, operator: '<=', value });
return this;
}
// 包含条件
whereIn(field, values) {
this.query.filters.push({ field, operator: 'in', value: values });
return this;
}
// 范围条件
whereBetween(field, min, max) {
this.query.filters.push({ field, operator: 'between', value: { min, max } });
return this;
}
// 字符串包含
whereContains(field, substring) {
this.query.filters.push({ field, operator: 'contains', value: substring });
return this;
}
// 字符串开头
whereStartsWith(field, prefix) {
this.query.filters.push({ field, operator: 'startsWith', value: prefix });
return this;
}
// 字符串结尾
whereEndsWith(field, suffix) {
this.query.filters.push({ field, operator: 'endsWith', value: suffix });
return this;
}
// 排序
orderBy(field, direction = 'asc') {
this.query.sort = { field, direction };
return this;
}
// 限制数量
limit(count) {
this.query.limit = count;
return this;
}
// 偏移量
offset(count) {
this.query.offset = count;
return this;
}
// 使用索引
useIndex(indexName) {
this.query.indexes.push(indexName);
return this;
}
// 执行查询
async execute() {
if (this.query.filters.length === 0) {
// 无过滤器,直接获取所有数据
return this.dbManager.getAll(this.storeName, null, this.query.limit);
}
// 使用游标进行复杂查询
return new Promise((resolve, reject) => {
this.dbManager.cursor(this.storeName, (record, cursor) => {
const results = [];
let count = 0;
let skipped = 0;
const processRecord = (record) => {
// 检查偏移量
if (skipped < this.query.offset) {
skipped++;
return false;
}
// 检查限制
if (this.query.limit && count >= this.query.limit) {
return false;
}
// 应用过滤器
if (this.matchesFilters(record)) {
results.push(record);
count++;
}
return true;
};
if (processRecord(record)) {
cursor.continue();
} else {
resolve(results);
}
}, {
indexName: this.query.indexes[0] || null,
direction: this.query.sort?.direction === 'desc' ? 'prev' : 'next'
}).then(resolve).catch(reject);
});
}
// 检查记录是否匹配所有过滤器
matchesFilters(record) {
return this.query.filters.every(filter => {
const value = record[filter.field];
switch (filter.operator) {
case '=':
return value === filter.value;
case '!=':
return value !== filter.value;
case '>':
return value > filter.value;
case '<':
return value < filter.value;
case '>=':
return value >= filter.value;
case '<=':
return value <= filter.value;
case 'in':
return Array.isArray(filter.value) && filter.value.includes(value);
case 'between':
return value >= filter.value.min && value <= filter.value.max;
case 'contains':
return typeof value === 'string' && value.includes(filter.value);
case 'startsWith':
return typeof value === 'string' && value.startsWith(filter.value);
case 'endsWith':
return typeof value === 'string' && value.endsWith(filter.value);
default:
return true;
}
});
}
// 计数
async count() {
const results = await this.execute();
return results.length;
}
// 获取第一条记录
async first() {
const results = await this.limit(1).execute();
return results[0] || null;
}
// 获取最后一条记录
async last() {
const results = await this.execute();
return results[results.length - 1] || null;
}
// 检查是否存在
async exists() {
const result = await this.first();
return result !== null;
}
// 更新匹配的记录
async update(updates) {
const results = await this.execute();
const updatePromises = results.map(record => {
const key = record.id || record[this.dbManager.stores.get(this.storeName).keyPath];
return this.dbManager.update(this.storeName, key, updates);
});
return Promise.all(updatePromises);
}
// 删除匹配的记录
async delete() {
const results = await this.execute();
const deletePromises = results.map(record => {
const key = record.id || record[this.dbManager.stores.get(this.storeName).keyPath];
return this.dbManager.delete(this.storeName, key);
});
return Promise.all(deletePromises);
}
// 分页查询
async paginate(page = 1, perPage = 20) {
const offset = (page - 1) * perPage;
const results = await this.offset(offset).limit(perPage).execute();
const total = await this.count();
return {
data: results,
pagination: {
page,
perPage,
total,
totalPages: Math.ceil(total / perPage),
hasNext: page * perPage < total,
hasPrev: page > 1
}
};
}
}
// 使用示例
class QueryBuilderExample {
static async demonstrate(dbManager) {
const queryBuilder = new IndexedDBQueryBuilder(dbManager, 'products');
// 复杂查询
const results = await queryBuilder
.where('category', 'electronics')
.whereGreaterThan('price', 50)
.whereLessThan('price', 200)
.orderBy('price', 'desc')
.limit(10)
.execute();
console.log('Query results:', results);
// 分页查询
const page = await queryBuilder
.where('category', 'electronics')
.paginate(1, 5);
console.log('Pagination:', page);
return results;
}
}
三、Cookie 操作库完整实现
3.1 完整的 Cookie 管理器
javascript
class CookieManager {
constructor(options = {}) {
this.defaults = {
path: '/',
domain: window.location.hostname,
secure: window.location.protocol === 'https:',
sameSite: 'Lax',
expires: 7, // 默认7天
...options
};
this.observers = new Map();
this.encryptionEnabled = false;
this.compressionEnabled = false;
// 监听 cookie 变化
this.setupMutationObserver();
}
// 设置 Cookie
set(name, value, options = {}) {
const config = { ...this.defaults, ...options };
let cookieValue = value;
// 序列化非字符串值
if (typeof value !== 'string') {
cookieValue = JSON.stringify(value);
}
// 加密
if (config.encrypt && this.encryptionEnabled) {
cookieValue = this.encrypt(cookieValue, config.encryptionKey);
}
// 压缩
if (config.compress && this.compressionEnabled && cookieValue.length > 100) {
cookieValue = this.compress(cookieValue);
}
// 构建 cookie 字符串
let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(cookieValue)}`;
// 添加过期时间
if (config.expires) {
let expires = config.expires;
if (typeof expires === 'number') {
const date = new Date();
date.setTime(date.getTime() + expires * 24 * 60 * 60 * 1000);
expires = date.toUTCString();
} else if (expires instanceof Date) {
expires = expires.toUTCString();
}
cookieString += `; expires=${expires}`;
}
// 添加路径
if (config.path) {
cookieString += `; path=${config.path}`;
}
// 添加域名
if (config.domain) {
cookieString += `; domain=${config.domain}`;
}
// 添加安全标志
if (config.secure) {
cookieString += '; secure';
}
// 添加 SameSite
if (config.sameSite) {
cookieString += `; samesite=${config.sameSite}`;
}
// 设置 cookie
document.cookie = cookieString;
// 通知观察者
this.notifyObservers(name, value, 'set');
return true;
}
// 获取 Cookie
get(name, defaultValue = null) {
const cookies = this.getAll();
if (!(name in cookies)) {
return defaultValue;
}
let value = cookies[name];
// 解压缩
if (this.isCompressed(value)) {
value = this.decompress(value);
}
// 解密
if (this.isEncrypted(value)) {
value = this.decrypt(value);
}
// 尝试解析 JSON
try {
return JSON.parse(value);
} catch {
return value;
}
}
// 获取所有 Cookie
getAll() {
const cookies = {};
if (!document.cookie) {
return cookies;
}
document.cookie.split(';').forEach(cookie => {
const [name, ...valueParts] = cookie.trim().split('=');
const value = valueParts.join('='); // 处理值中包含等号的情况
try {
cookies[decodeURIComponent(name)] = decodeURIComponent(value);
} catch {
cookies[name] = value;
}
});
return cookies;
}
// 删除 Cookie
remove(name, options = {}) {
const config = {
path: this.defaults.path,
domain: this.defaults.domain,
...options
};
// 设置过期时间为过去的时间
this.set(name, '', {
...config,
expires: new Date(0)
});
// 通知观察者
this.notifyObservers(name, null, 'remove');
return true;
}
// 检查 Cookie 是否存在
has(name) {
return name in this.getAll();
}
// 启用加密
enableEncryption(encryptionKey) {
if (typeof window.crypto === 'undefined') {
console.warn('Web Crypto API not available, encryption disabled');
return false;
}
this.encryptionEnabled = true;
this.encryptionKey = encryptionKey;
return true;
}
// 启用压缩
enableCompression() {
this.compressionEnabled = true;
return true;
}
// 加密数据
encrypt(text, key = this.encryptionKey) {
if (!this.encryptionEnabled) return text;
try {
// 简单的 XOR 加密(实际项目中应使用更安全的算法)
let result = '';
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
result += String.fromCharCode(charCode);
}
return `encrypted:${btoa(result)}`;
} catch (error) {
console.error('Encryption failed:', error);
return text;
}
}
// 解密数据
decrypt(text, key = this.encryptionKey) {
if (!text.startsWith('encrypted:')) return text;
try {
const encrypted = atob(text.slice(10));
let result = '';
for (let i = 0; i < encrypted.length; i++) {
const charCode = encrypted.charCodeAt(i) ^ key.charCodeAt(i % key.length);
result += String.fromCharCode(charCode);
}
return result;
} catch (error) {
console.error('Decryption failed:', error);
return text;
}
}
// 判断是否加密
isEncrypted(text) {
return text.startsWith('encrypted:');
}
// 压缩数据
compress(text) {
if (!this.compressionEnabled) return text;
try {
// 简单的压缩(实际项目中可使用 pako 等库)
const compressed = btoa(encodeURIComponent(text).replace(/%[0-9A-F]{2}/g, ''));
return `compressed:${compressed}`;
} catch (error) {
console.error('Compression failed:', error);
return text;
}
}
// 解压缩数据
decompress(text) {
if (!text.startsWith('compressed:')) return text;
try {
const compressed = text.slice(11);
return decodeURIComponent(atob(compressed));
} catch (error) {
console.error('Decompression failed:', error);
return text;
}
}
// 判断是否压缩
isCompressed(text) {
return text.startsWith('compressed:');
}
// 添加观察者
observe(name, callback) {
if (!this.observers.has(name)) {
this.observers.set(name, new Set());
}
this.observers.get(name).add(callback);
// 返回取消观察的函数
return () => {
const observers = this.observers.get(name);
if (observers) {
observers.delete(callback);
if (observers.size === 0) {
this.observers.delete(name);
}
}
};
}
// 通知观察者
notifyObservers(name, value, action) {
const observers = this.observers.get(name);
if (observers) {
observers.forEach(callback => {
callback(value, action, name);
});
}
// 通知全局观察者
const globalObservers = this.observers.get('*');
if (globalObservers) {
globalObservers.forEach(callback => {
callback(value, action, name);
});
}
}
// 设置 MutationObserver 监听 cookie 变化
setupMutationObserver() {
// 由于 cookie 变化不会触发 DOM 事件,我们需要轮询检查变化
this.lastCookieString = document.cookie;
this.pollingInterval = setInterval(() => {
const currentCookieString = document.cookie;
if (currentCookieString !== this.lastCookieString) {
this.detectCookieChanges(this.lastCookieString, currentCookieString);
this.lastCookieString = currentCookieString;
}
}, 1000); // 每秒检查一次
}
// 检测 cookie 变化
detectCookieChanges(oldCookieString, newCookieString) {
const oldCookies = this.parseCookieString(oldCookieString);
const newCookies = this.parseCookieString(newCookieString);
// 检测新增或修改的 cookie
Object.keys(newCookies).forEach(name => {
if (!oldCookies[name] || oldCookies[name] !== newCookies[name]) {
this.notifyObservers(name, newCookies[name], oldCookies[name] ? 'update' : 'set');
}
});
// 检测删除的 cookie
Object.keys(oldCookies).forEach(name => {
if (!newCookies[name]) {
this.notifyObservers(name, null, 'remove');
}
});
}
// 解析 cookie 字符串
parseCookieString(cookieString) {
const cookies = {};
if (!cookieString) return cookies;
cookieString.split(';').forEach(cookie => {
const [name, ...valueParts] = cookie.trim().split('=');
const value = valueParts.join('=');
if (name) {
try {
cookies[decodeURIComponent(name)] = decodeURIComponent(value);
} catch {
cookies[name] = value;
}
}
});
return cookies;
}
// 批量设置 cookie
batchSet(cookies, commonOptions = {}) {
const results = {};
Object.entries(cookies).forEach(([name, value]) => {
let options = commonOptions;
let actualValue = value;
// 如果值是对象,可能包含选项
if (value && typeof value === 'object' && !Array.isArray(value)) {
if (value.value !== undefined) {
actualValue = value.value;
options = { ...commonOptions, ...value.options };
}
}
results[name] = this.set(name, actualValue, options);
});
return results;
}
// 获取 cookie 大小信息
getStats() {
const cookies = this.getAll();
let totalSize = 0;
Object.entries(cookies).forEach(([name, value]) => {
// 每个字符在 cookie 中通常占用 1-4 字节,这里估算为 2 字节
totalSize += (name.length + value.length) * 2;
});
return {
count: Object.keys(cookies).length,
totalSize,
estimatedRemaining: 4096 - totalSize, // cookie 通常有 4KB 限制
usagePercentage: (totalSize / 4096) * 100
};
}
// 导出所有 cookie
export() {
const cookies = this.getAll();
const exported = {};
Object.entries(cookies).forEach(([name, value]) => {
exported[name] = value;
});
return {
timestamp: Date.now(),
count: Object.keys(cookies).length,
cookies: exported
};
}
// 从备份导入 cookie
import(backup) {
if (!backup || !backup.cookies) {
throw new Error('Invalid backup data');
}
Object.entries(backup.cookies).forEach(([name, value]) => {
this.set(name, value);
});
return true;
}
// 销毁实例
destroy() {
if (this.pollingInterval) {
clearInterval(this.pollingInterval);
}
this.observers.clear();
}
}
// 使用示例
class CookieExample {
static demonstrate() {
// 创建 cookie 管理器
const cookieManager = new CookieManager({
path: '/',
expires: 30, // 30 天
sameSite: 'Strict'
});
// 启用加密
cookieManager.enableEncryption('my-secret-key');
// 启用压缩
cookieManager.enableCompression();
// 设置 cookie
cookieManager.set('user', {
id: 123,
name: 'John Doe',
preferences: { theme: 'dark', language: 'en' }
}, { encrypt: true });
cookieManager.set('session_token', 'abc123def456', {
secure: true,
expires: 1 // 1 天
});
// 批量设置
cookieManager.batchSet({
'theme': 'dark',
'language': 'en-US',
'notifications': true
}, { expires: 7 });
// 获取 cookie
const user = cookieManager.get('user');
const theme = cookieManager.get('theme', 'light');
console.log('User:', user);
console.log('Theme:', theme);
// 观察 cookie 变化
const unsubscribe = cookieManager.observe('theme', (value, action) => {
console.log(`Theme cookie ${action}:`, value);
});
// 获取统计信息
const stats = cookieManager.getStats();
console.log('Cookie stats:', stats);
// 导出所有 cookie
const backup = cookieManager.export();
console.log('Cookie backup:', backup);
// 取消观察
unsubscribe();
return cookieManager;
}
}
3.2 Cookie 同源和跨域处理
javascript
class CrossDomainCookieManager {
constructor() {
this.iframe = null;
this.messageHandlers = new Map();
this.ready = false;
this.targetOrigin = '*';
// 设置消息监听
window.addEventListener('message', this.handleMessage.bind(this), false);
}
// 初始化跨域通信
init(targetDomain, options = {}) {
return new Promise((resolve, reject) => {
this.targetOrigin = targetDomain;
// 创建隐藏的 iframe
this.iframe = document.createElement('iframe');
this.iframe.style.display = 'none';
this.iframe.src = `${targetDomain}/cookie-proxy.html`;
this.iframe.onload = () => {
this.ready = true;
console.log('Cookie proxy iframe loaded');
resolve(this);
};
this.iframe.onerror = (error) => {
reject(new Error('Failed to load cookie proxy iframe'));
};
document.body.appendChild(this.iframe);
});
}
// 处理消息
handleMessage(event) {
// 验证来源
if (this.targetOrigin !== '*' && event.origin !== this.targetOrigin) {
return;
}
const { type, id, data, error } = event.data;
if (!type || !id) return;
const handler = this.messageHandlers.get(id);
if (handler) {
if (error) {
handler.reject(new Error(error));
} else {
handler.resolve(data);
}
this.messageHandlers.delete(id);
}
}
// 发送消息到 iframe
sendMessage(type, data) {
return new Promise((resolve, reject) => {
if (!this.ready || !this.iframe) {
reject(new Error('Cookie proxy not ready'));
return;
}
const id = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.messageHandlers.set(id, { resolve, reject });
this.iframe.contentWindow.postMessage({
type,
id,
data
}, this.targetOrigin);
// 设置超时
setTimeout(() => {
if (this.messageHandlers.has(id)) {
this.messageHandlers.delete(id);
reject(new Error('Request timeout'));
}
}, 5000);
});
}
// 设置跨域 cookie
async setCookie(name, value, options = {}) {
return this.sendMessage('SET_COOKIE', { name, value, options });
}
// 获取跨域 cookie
async getCookie(name, defaultValue = null) {
try {
const value = await this.sendMessage('GET_COOKIE', { name });
return value || defaultValue;
} catch (error) {
console.error('Failed to get cross-domain cookie:', error);
return defaultValue;
}
}
// 删除跨域 cookie
async removeCookie(name, options = {}) {
return this.sendMessage('REMOVE_COOKIE', { name, options });
}
// 获取所有跨域 cookie
async getAllCookies() {
return this.sendMessage('GET_ALL_COOKIES', {});
}
// 销毁
destroy() {
if (this.iframe) {
document.body.removeChild(this.iframe);
this.iframe = null;
}
window.removeEventListener('message', this.handleMessage);
this.messageHandlers.clear();
this.ready = false;
}
}
// Cookie 代理页面代码(需要部署在目标域)
class CookieProxy {
constructor() {
this.initMessageHandler();
}
initMessageHandler() {
window.addEventListener('message', async (event) => {
const { type, id, data } = event.data;
if (!type || !id) return;
try {
let result;
switch (type) {
case 'SET_COOKIE':
result = this.setCookie(data.name, data.value, data.options);
break;
case 'GET_COOKIE':
result = this.getCookie(data.name);
break;
case 'REMOVE_COOKIE':
result = this.removeCookie(data.name, data.options);
break;
case 'GET_ALL_COOKIES':
result = this.getAllCookies();
break;
default:
throw new Error(`Unknown message type: ${type}`);
}
// 发送响应
event.source.postMessage({
type: 'RESPONSE',
id,
data: await result
}, event.origin);
} catch (error) {
event.source.postMessage({
type: 'ERROR',
id,
error: error.message
}, event.origin);
}
}, false);
}
// 设置 cookie(本地)
setCookie(name, value, options = {}) {
const cookieManager = new CookieManager();
return cookieManager.set(name, value, options);
}
// 获取 cookie(本地)
getCookie(name) {
const cookieManager = new CookieManager();
return cookieManager.get(name);
}
// 删除 cookie(本地)
removeCookie(name, options = {}) {
const cookieManager = new CookieManager();
return cookieManager.remove(name, options);
}
// 获取所有 cookie(本地)
getAllCookies() {
const cookieManager = new CookieManager();
return cookieManager.getAll();
}
}
// 使用示例
class CrossDomainExample {
static async demonstrate() {
// 主域中的代码
const crossDomainManager = new CrossDomainCookieManager();
try {
// 初始化连接(假设子域为 https://api.example.com)
await crossDomainManager.init('https://api.example.com');
// 在子域中设置 cookie
await crossDomainManager.setCookie('api_token', 'secret_token_123', {
expires: 7,
secure: true
});
// 从子域获取 cookie
const token = await crossDomainManager.getCookie('api_token');
console.log('API token from subdomain:', token);
// 获取子域中的所有 cookie
const allCookies = await crossDomainManager.getAllCookies();
console.log('All cookies from subdomain:', allCookies);
return crossDomainManager;
} catch (error) {
console.error('Cross-domain cookie operation failed:', error);
return null;
}
}
}
四、存储安全性与最佳实践
4.1 安全存储包装器
javascript
class SecureStorage {
constructor(options = {}) {
this.options = {
encryptionAlgorithm: 'AES-GCM',
keyDerivationIterations: 100000,
storageBackend: 'auto', // 'localStorage', 'sessionStorage', 'indexedDB', 'auto'
...options
};
this.keyCache = new Map();
this.encryptionSupported = this.detectCryptoSupport();
this.storageBackend = this.selectStorageBackend();
if (this.encryptionSupported) {
this.initCryptoKeys();
}
}
// 检测加密支持
detectCryptoSupport() {
return typeof window.crypto !== 'undefined' &&
typeof window.crypto.subtle !== 'undefined';
}
// 选择存储后端
selectStorageBackend() {
if (this.options.storageBackend !== 'auto') {
return this.options.storageBackend;
}
// 自动选择最佳存储后端
try {
if (typeof window.indexedDB !== 'undefined') {
return 'indexedDB';
} else if (typeof window.localStorage !== 'undefined') {
// 测试 localStorage 是否可用
window.localStorage.setItem('test', 'test');
window.localStorage.removeItem('test');
return 'localStorage';
} else if (typeof window.sessionStorage !== 'undefined') {
return 'sessionStorage';
}
} catch (error) {
console.warn('Storage detection failed:', error);
}
return 'memory';
}
// 初始化加密密钥
async initCryptoKeys() {
try {
// 从安全来源获取或生成密钥
this.masterKey = await this.deriveKeyFromPassword(
this.options.encryptionPassword || 'default-password',
'master-key'
);
console.log('Crypto keys initialized');
} catch (error) {
console.error('Failed to initialize crypto keys:', error);
this.encryptionSupported = false;
}
}
// 从密码派生密钥
async deriveKeyFromPassword(password, salt) {
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password);
const saltBuffer = encoder.encode(salt);
// 导入密码作为密钥材料
const keyMaterial = await crypto.subtle.importKey(
'raw',
passwordBuffer,
'PBKDF2',
false,
['deriveKey']
);
// 派生密钥
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: saltBuffer,
iterations: this.options.keyDerivationIterations,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
return key;
}
// 生成数据特定密钥
async getDataKey(keyName) {
if (this.keyCache.has(keyName)) {
return this.keyCache.get(keyName);
}
const key = await this.deriveKeyFromPassword(
this.options.encryptionPassword || 'default-password',
`data-key-${keyName}`
);
this.keyCache.set(keyName, key);
return key;
}
// 加密数据
async encryptData(data, keyName = 'default') {
if (!this.encryptionSupported) {
return data;
}
try {
const key = await this.getDataKey(keyName);
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(JSON.stringify(data));
// 生成随机初始化向量
const iv = crypto.getRandomValues(new Uint8Array(12));
// 加密数据
const encryptedBuffer = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
dataBuffer
);
// 组合 IV 和加密数据
const combinedBuffer = new Uint8Array(iv.length + encryptedBuffer.byteLength);
combinedBuffer.set(iv, 0);
combinedBuffer.set(new Uint8Array(encryptedBuffer), iv.length);
// 转换为 base64
const base64String = btoa(String.fromCharCode.apply(null, combinedBuffer));
return {
encrypted: true,
algorithm: this.options.encryptionAlgorithm,
data: base64String,
keyName
};
} catch (error) {
console.error('Encryption failed:', error);
return data;
}
}
// 解密数据
async decryptData(encryptedData, keyName = 'default') {
if (!encryptedData.encrypted || !this.encryptionSupported) {
return encryptedData;
}
try {
const key = await this.getDataKey(keyName);
// 从 base64 解码
const combinedBuffer = Uint8Array.from(atob(encryptedData.data), c => c.charCodeAt(0));
// 提取 IV 和加密数据
const iv = combinedBuffer.slice(0, 12);
const encryptedBuffer = combinedBuffer.slice(12);
// 解密数据
const decryptedBuffer = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
encryptedBuffer
);
const decoder = new TextDecoder();
const decryptedString = decoder.decode(decryptedBuffer);
return JSON.parse(decryptedString);
} catch (error) {
console.error('Decryption failed:', error);
return encryptedData;
}
}
// 安全存储数据
async set(key, value, options = {}) {
const {
encrypt = true,
ttl = null,
keyName = 'default',
...storageOptions
} = options;
let processedValue = value;
// 加密数据
if (encrypt && this.encryptionSupported) {
processedValue = await this.encryptData(value, keyName);
}
// 添加元数据
const storageItem = {
value: processedValue,
meta: {
encrypted: encrypt,
keyName,
createdAt: Date.now(),
expiresAt: ttl ? Date.now() + ttl : null,
version: '1.0'
}
};
// 存储到后端
return this.storeToBackend(key, storageItem, storageOptions);
}
// 从后端存储数据
async storeToBackend(key, value, options) {
switch (this.storageBackend) {
case 'indexedDB':
return this.storeToIndexedDB(key, value, options);
case 'localStorage':
case 'sessionStorage':
return this.storeToWebStorage(key, value, options);
default:
return this.storeToMemory(key, value, options);
}
}
// 存储到 IndexedDB
async storeToIndexedDB(key, value, options) {
// 简化的 IndexedDB 存储
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureStorageDB', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('secureData')) {
db.createObjectStore('secureData', { keyPath: 'key' });
}
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['secureData'], 'readwrite');
const store = transaction.objectStore('secureData');
const item = { key, ...value };
const putRequest = store.put(item);
putRequest.onsuccess = () => resolve(true);
putRequest.onerror = (error) => reject(error);
};
request.onerror = (error) => reject(error);
});
}
// 存储到 Web Storage
storeToWebStorage(key, value, options) {
const storage = this.storageBackend === 'localStorage' ?
window.localStorage : window.sessionStorage;
try {
storage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error('Web storage failed:', error);
return false;
}
}
// 存储到内存
storeToMemory(key, value, options) {
this.memoryStorage = this.memoryStorage || new Map();
this.memoryStorage.set(key, value);
return true;
}
// 安全获取数据
async get(key, defaultValue = null) {
// 从后端获取数据
const storageItem = await this.getFromBackend(key);
if (!storageItem) {
return defaultValue;
}
// 检查是否过期
if (storageItem.meta.expiresAt && Date.now() > storageItem.meta.expiresAt) {
await this.remove(key);
return defaultValue;
}
let value = storageItem.value;
// 解密数据
if (storageItem.meta.encrypted) {
value = await this.decryptData(value, storageItem.meta.keyName);
}
return value;
}
// 从后端获取数据
async getFromBackend(key) {
switch (this.storageBackend) {
case 'indexedDB':
return this.getFromIndexedDB(key);
case 'localStorage':
case 'sessionStorage':
return this.getFromWebStorage(key);
default:
return this.getFromMemory(key);
}
}
// 从 IndexedDB 获取
async getFromIndexedDB(key) {
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureStorageDB', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['secureData'], 'readonly');
const store = transaction.objectStore('secureData');
const getRequest = store.get(key);
getRequest.onsuccess = (event) => {
resolve(event.target.result);
};
getRequest.onerror = (error) => reject(error);
};
request.onerror = (error) => reject(error);
});
}
// 从 Web Storage 获取
getFromWebStorage(key) {
const storage = this.storageBackend === 'localStorage' ?
window.localStorage : window.sessionStorage;
try {
const item = storage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error('Web storage read failed:', error);
return null;
}
}
// 从内存获取
getFromMemory(key) {
return this.memoryStorage ? this.memoryStorage.get(key) || null : null;
}
// 删除数据
async remove(key) {
switch (this.storageBackend) {
case 'indexedDB':
return this.removeFromIndexedDB(key);
case 'localStorage':
case 'sessionStorage':
return this.removeFromWebStorage(key);
default:
return this.removeFromMemory(key);
}
}
// 从 IndexedDB 删除
async removeFromIndexedDB(key) {
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureStorageDB', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['secureData'], 'readwrite');
const store = transaction.objectStore('secureData');
const deleteRequest = store.delete(key);
deleteRequest.onsuccess = () => resolve(true);
deleteRequest.onerror = (error) => reject(error);
};
request.onerror = (error) => reject(error);
});
}
// 从 Web Storage 删除
removeFromWebStorage(key) {
const storage = this.storageBackend === 'localStorage' ?
window.localStorage : window.sessionStorage;
try {
storage.removeItem(key);
return true;
} catch (error) {
console.error('Web storage delete failed:', error);
return false;
}
}
// 从内存删除
removeFromMemory(key) {
if (this.memoryStorage) {
return this.memoryStorage.delete(key);
}
return true;
}
// 清空所有数据
async clear() {
switch (this.storageBackend) {
case 'indexedDB':
return this.clearIndexedDB();
case 'localStorage':
case 'sessionStorage':
return this.clearWebStorage();
default:
return this.clearMemory();
}
}
// 清空 IndexedDB
async clearIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureStorageDB', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['secureData'], 'readwrite');
const store = transaction.objectStore('secureData');
const clearRequest = store.clear();
clearRequest.onsuccess = () => resolve(true);
clearRequest.onerror = (error) => reject(error);
};
request.onerror = (error) => reject(error);
});
}
// 清空 Web Storage
clearWebStorage() {
const storage = this.storageBackend === 'localStorage' ?
window.localStorage : window.sessionStorage;
try {
storage.clear();
return true;
} catch (error) {
console.error('Web storage clear failed:', error);
return false;
}
}
// 清空内存
clearMemory() {
if (this.memoryStorage) {
this.memoryStorage.clear();
}
return true;
}
// 获取存储统计信息
async getStats() {
const stats = {
backend: this.storageBackend,
encryptionSupported: this.encryptionSupported,
items: 0,
size: 0
};
// 根据后端获取统计信息
switch (this.storageBackend) {
case 'indexedDB':
const dbItems = await this.getIndexedDBStats();
stats.items = dbItems.count;
stats.size = dbItems.size;
break;
case 'localStorage':
case 'sessionStorage':
const wsStats = this.getWebStorageStats();
stats.items = wsStats.items;
stats.size = wsStats.size;
break;
default:
stats.items = this.memoryStorage ? this.memoryStorage.size : 0;
stats.size = 0; // 内存大小难以准确计算
}
return stats;
}
// 获取 IndexedDB 统计信息
async getIndexedDBStats() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('SecureStorageDB', 1);
request.onsuccess = async (event) => {
const db = event.target.result;
const transaction = db.transaction(['secureData'], 'readonly');
const store = transaction.objectStore('secureData');
const countRequest = store.count();
countRequest.onsuccess = (event) => {
resolve({
count: event.target.result,
size: 0 // 实际项目中可以遍历计算大小
});
};
countRequest.onerror = (error) => reject(error);
};
request.onerror = (error) => reject(error);
});
}
// 获取 Web Storage 统计信息
getWebStorageStats() {
const storage = this.storageBackend === 'localStorage' ?
window.localStorage : window.sessionStorage;
let items = 0;
let size = 0;
for (let i = 0; i < storage.length; i++) {
const key = storage.key(i);
const value = storage.getItem(key);
items++;
size += (key.length + value.length) * 2; // UTF-16 估算
}
return { items, size };
}
}
// 使用示例
class SecureStorageExample {
static async demonstrate() {
// 创建安全存储实例
const secureStorage = new SecureStorage({
encryptionPassword: 'strong-password-123',
storageBackend: 'auto'
});
// 存储敏感数据
await secureStorage.set('credit_card', {
number: '4111111111111111',
expiry: '12/25',
cvv: '123'
}, {
encrypt: true,
ttl: 3600000, // 1小时过期
keyName: 'financial'
});
// 存储非敏感数据
await secureStorage.set('user_preferences', {
theme: 'dark',
language: 'en-US',
notifications: true
}, { encrypt: false });
// 获取数据
const cardData = await secureStorage.get('credit_card');
const preferences = await secureStorage.get('user_preferences');
console.log('Credit card data:', cardData);
console.log('User preferences:', preferences);
// 获取统计信息
const stats = await secureStorage.getStats();
console.log('Storage stats:', stats);
return secureStorage;
}
}
五、存储性能监控和调优
5.1 存储性能监控器
javascript
class StoragePerformanceMonitor {
constructor() {
this.metrics = {
operations: {
read: { count: 0, totalTime: 0, failures: 0 },
write: { count: 0, totalTime: 0, failures: 0 },
delete: { count: 0, totalTime: 0, failures: 0 }
},
sizes: {
localStorage: 0,
sessionStorage: 0,
cookies: 0,
indexedDB: 0
},
limits: {
localStorage: 5 * 1024 * 1024, // 5MB
sessionStorage: 5 * 1024 * 1024, // 5MB
cookies: 4096, // 4KB
indexedDB: 0 // 无明确限制
}
};
this.sampleInterval = null;
this.history = [];
this.maxHistorySize = 1000;
this.startMonitoring();
}
// 开始监控
startMonitoring(interval = 5000) {
this.sampleInterval = setInterval(() => {
this.captureSnapshot();
}, interval);
// 初始快照
this.captureSnapshot();
// 监听存储事件
window.addEventListener('storage', this.handleStorageEvent.bind(this));
// 监听 beforeunload 以保存最终状态
window.addEventListener('beforeunload', () => {
this.saveMetricsToLocalStorage();
});
}
// 停止监控
stopMonitoring() {
if (this.sampleInterval) {
clearInterval(this.sampleInterval);
this.sampleInterval = null;
}
window.removeEventListener('storage', this.handleStorageEvent);
window.removeEventListener('beforeunload', this.saveMetricsToLocalStorage);
}
// 处理存储事件
handleStorageEvent(event) {
const metricType = event.newValue ? 'write' : 'delete';
this.recordOperation(metricType, event.key, 0, false);
}
// 记录操作
recordOperation(type, key, duration, success = true) {
const operation = this.metrics.operations[type];
if (operation) {
operation.count++;
operation.totalTime += duration;
if (!success) {
operation.failures++;
}
}
// 添加到历史记录
this.addToHistory({
timestamp: Date.now(),
type,
key,
duration,
success
});
}
// 添加到历史记录
addToHistory(entry) {
this.history.push(entry);
// 限制历史记录大小
if (this.history.length > this.maxHistorySize) {
this.history = this.history.slice(-this.maxHistorySize);
}
}
// 捕获快照
captureSnapshot() {
const snapshot = {
timestamp: Date.now(),
metrics: { ...this.metrics },
sizes: this.calculateSizes(),
performance: this.calculatePerformance()
};
// 更新当前大小
this.metrics.sizes = snapshot.sizes;
// 保存到历史
this.history.push(snapshot);
// 限制历史记录大小
if (this.history.length > this.maxHistorySize) {
this.history = this.history.slice(-this.maxHistorySize);
}
return snapshot;
}
// 计算存储大小
calculateSizes() {
const sizes = {
localStorage: 0,
sessionStorage: 0,
cookies: 0,
indexedDB: 0
};
// 计算 localStorage 大小
try {
let localStorageSize = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
localStorageSize += (key.length + value.length) * 2;
}
sizes.localStorage = localStorageSize;
} catch (error) {
console.warn('Failed to calculate localStorage size:', error);
}
// 计算 sessionStorage 大小
try {
let sessionStorageSize = 0;
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
const value = sessionStorage.getItem(key);
sessionStorageSize += (key.length + value.length) * 2;
}
sizes.sessionStorage = sessionStorageSize;
} catch (error) {
console.warn('Failed to calculate sessionStorage size:', error);
}
// 计算 cookies 大小
try {
sizes.cookies = document.cookie.length * 2; // 简单估算
} catch (error) {
console.warn('Failed to calculate cookies size:', error);
}
return sizes;
}
// 计算性能指标
calculatePerformance() {
const perf = {
averageReadTime: 0,
averageWriteTime: 0,
readSuccessRate: 1,
writeSuccessRate: 1,
storageUsage: {}
};
const readOps = this.metrics.operations.read;
const writeOps = this.metrics.operations.write;
if (readOps.count > 0) {
perf.averageReadTime = readOps.totalTime / readOps.count;
perf.readSuccessRate = (readOps.count - readOps.failures) / readOps.count;
}
if (writeOps.count > 0) {
perf.averageWriteTime = writeOps.totalTime / writeOps.count;
perf.writeSuccessRate = (writeOps.count - writeOps.failures) / writeOps.count;
}
// 计算存储使用率
Object.keys(this.metrics.sizes).forEach(storageType => {
const size = this.metrics.sizes[storageType];
const limit = this.metrics.limits[storageType];
if (limit > 0) {
perf.storageUsage[storageType] = {
size,
limit,
percentage: (size / limit) * 100,
remaining: limit - size
};
} else {
perf.storageUsage[storageType] = {
size,
limit: 'unlimited',
percentage: 0,
remaining: Infinity
};
}
});
return perf;
}
// 获取性能报告
getReport() {
const snapshot = this.captureSnapshot();
return {
summary: {
totalOperations: Object.values(this.metrics.operations).reduce(
(sum, op) => sum + op.count, 0
),
totalFailures: Object.values(this.metrics.operations).reduce(
(sum, op) => sum + op.failures, 0
),
overallSuccessRate: 1 - (
Object.values(this.metrics.operations).reduce(
(sum, op) => sum + op.failures, 0
) / Math.max(1, Object.values(this.metrics.operations).reduce(
(sum, op) => sum + op.count, 0
))
),
monitoringDuration: this.history.length > 0 ?
Date.now() - this.history[0].timestamp : 0
},
current: snapshot,
trends: this.calculateTrends(),
recommendations: this.generateRecommendations()
};
}
// 计算趋势
calculateTrends() {
if (this.history.length < 2) return {};
const recent = this.history.slice(-10); // 最近10个样本
const trends = {};
// 计算操作趋势
['read', 'write', 'delete'].forEach(opType => {
const counts = recent.map(s => s.metrics.operations[opType].count);
const avgCount = counts.reduce((a, b) => a + b, 0) / counts.length;
trends[opType] = {
averagePerSample: avgCount,
trend: counts.length > 1 ?
(counts[counts.length - 1] - counts[0]) / counts[0] : 0
};
});
// 计算大小趋势
Object.keys(this.metrics.sizes).forEach(storageType => {
const sizes = recent.map(s => s.sizes[storageType]);
trends[storageType] = {
averageSize: sizes.reduce((a, b) => a + b, 0) / sizes.length,
growthRate: sizes.length > 1 ?
(sizes[sizes.length - 1] - sizes[0]) / sizes[0] : 0
};
});
return trends;
}
// 生成优化建议
generateRecommendations() {
const recommendations = [];
const perf = this.calculatePerformance();
// 检查存储使用率
Object.entries(perf.storageUsage).forEach(([storageType, usage]) => {
if (usage.percentage > 80) {
recommendations.push({
type: 'warning',
message: `${storageType} usage is at ${usage.percentage.toFixed(1)}%`,
suggestion: 'Consider cleaning up unused data or migrating to IndexedDB',
priority: 'high'
});
} else if (usage.percentage > 50) {
recommendations.push({
type: 'info',
message: `${storageType} usage is at ${usage.percentage.toFixed(1)}%`,
suggestion: 'Monitor storage usage and consider optimization',
priority: 'medium'
});
}
});
// 检查操作失败率
if (perf.readSuccessRate < 0.95) {
recommendations.push({
type: 'error',
message: `Read operation success rate is low: ${(perf.readSuccessRate * 100).toFixed(1)}%`,
suggestion: 'Check storage availability and error handling',
priority: 'high'
});
}
if (perf.writeSuccessRate < 0.95) {
recommendations.push({
type: 'error',
message: `Write operation success rate is low: ${(perf.writeSuccessRate * 100).toFixed(1)}%`,
suggestion: 'Storage may be full or corrupted. Consider cleanup.',
priority: 'high'
});
}
// 检查性能
if (perf.averageReadTime > 100) { // 超过100ms
recommendations.push({
type: 'warning',
message: `Average read time is high: ${perf.averageReadTime.toFixed(1)}ms`,
suggestion: 'Consider using IndexedDB for large datasets',
priority: 'medium'
});
}
if (perf.averageWriteTime > 100) { // 超过100ms
recommendations.push({
type: 'warning',
message: `Average write time is high: ${perf.averageWriteTime.toFixed(1)}ms`,
suggestion: 'Batch write operations or use web workers',
priority: 'medium'
});
}
return recommendations;
}
// 保存指标到本地存储
saveMetricsToLocalStorage() {
try {
const report = this.getReport();
localStorage.setItem('storage_performance_metrics', JSON.stringify(report));
localStorage.setItem('storage_performance_history', JSON.stringify(this.history));
} catch (error) {
console.warn('Failed to save metrics:', error);
}
}
// 从本地存储加载指标
loadMetricsFromLocalStorage() {
try {
const savedReport = localStorage.getItem('storage_performance_metrics');
const savedHistory = localStorage.getItem('storage_performance_history');
if (savedReport) {
const report = JSON.parse(savedReport);
// 合并指标
Object.assign(this.metrics, report.current.metrics);
}
if (savedHistory) {
this.history = JSON.parse(savedHistory);
}
} catch (error) {
console.warn('Failed to load metrics:', error);
}
}
// 重置指标
resetMetrics() {
this.metrics = {
operations: {
read: { count: 0, totalTime: 0, failures: 0 },
write: { count: 0, totalTime: 0, failures: 0 },
delete: { count: 0, totalTime: 0, failures: 0 }
},
sizes: {
localStorage: 0,
sessionStorage: 0,
cookies: 0,
indexedDB: 0
},
limits: {
localStorage: 5 * 1024 * 1024,
sessionStorage: 5 * 1024 * 1024,
cookies: 4096,
indexedDB: 0
}
};
this.history = [];
}
// 导出数据
exportData(format = 'json') {
const report = this.getReport();
switch (format) {
case 'json':
return JSON.stringify(report, null, 2);
case 'csv':
return this.convertToCSV(report);
default:
return report;
}
}
// 转换为CSV
convertToCSV(report) {
const rows = [];
// 添加摘要行
rows.push('Section,Key,Value');
rows.push(`Summary,Total Operations,${report.summary.totalOperations}`);
rows.push(`Summary,Total Failures,${report.summary.totalFailures}`);
rows.push(`Summary,Success Rate,${report.summary.overallSuccessRate}`);
// 添加性能行
rows.push('Performance,Average Read Time,${report.current.performance.averageReadTime}');
rows.push('Performance,Average Write Time,${report.current.performance.averageWriteTime}');
// 添加存储使用行
Object.entries(report.current.performance.storageUsage).forEach(([storage, usage]) => {
rows.push(`Storage Usage,${storage},${usage.percentage.toFixed(1)}%`);
});
return rows.join('\n');
}
}
// 使用示例
class StorageMonitorExample {
static demonstrate() {
// 创建性能监控器
const monitor = new StoragePerformanceMonitor();
// 模拟一些存储操作
setTimeout(() => {
// 记录读取操作
const readStart = performance.now();
localStorage.getItem('test_key');
const readDuration = performance.now() - readStart;
monitor.recordOperation('read', 'test_key', readDuration, true);
// 记录写入操作
const writeStart = performance.now();
localStorage.setItem('test_key', 'test_value');
const writeDuration = performance.now() - writeStart;
monitor.recordOperation('write', 'test_key', writeDuration, true);
}, 1000);
// 获取报告
setTimeout(() => {
const report = monitor.getReport();
console.log('Storage Performance Report:', report);
// 导出数据
const jsonExport = monitor.exportData('json');
console.log('JSON Export:', jsonExport);
// 停止监控
monitor.stopMonitoring();
}, 5000);
return monitor;
}
}
总结
本文全面探讨了浏览器存储相关的API和技术,包括:
- 本地存储封装: 增强的localStorage/sessionStorage封装,支持加密、压缩、过期时间等高级功能
- IndexedDB高级封装: 完整的数据库操作包装器,支持迁移、事务、查询构建等
- Cookie操作库: 完整的Cookie管理器,支持跨域操作和安全性增强
- 安全存储实践: 数据加密、密钥管理、安全最佳实践
- 存储性能监控: 实时监控存储使用情况和性能指标
- 智能存储选择器: 根据场景自动选择最佳存储方案
关键要点
- 安全性优先: 始终对敏感数据进行加密,避免在客户端存储敏感信息
- 容量管理: 监控存储使用情况,及时清理过期数据
- 性能优化: 批量操作、延迟写入、合理选择存储后端
- 兼容性考虑: 提供降级方案,确保在不支持某些API的浏览器中正常运作
- 用户体验: 异步操作避免阻塞UI,提供适当的加载状态
- 数据完整性: 实现数据验证、版本控制和迁移策略
最佳实践建议
- 分层存储策略:
- 频繁访问的小数据:使用内存缓存
- 用户会话数据:使用sessionStorage
- 长期偏好设置:使用localStorage
- 大量结构化数据:使用IndexedDB
- 身份验证令牌:使用HttpOnly Cookie
- 数据生命周期管理:
- 为所有存储数据设置合理的过期时间
- 实现自动清理机制
- 提供数据导出/导入功能
- 错误处理和降级:
- 捕获所有存储操作异常
- 提供友好的错误提示
- 实现降级到更简单的存储方案
- 隐私合规:
- 遵循GDPR、CCPA等隐私法规
- 提供用户数据清除功能
- 明确告知用户数据使用方式
通过合理使用这些技术和策略,开发者可以构建出既安全又高效的Web应用存储系统,提供优秀的用户体验。