前端本地存储进阶:IndexedDB 封装与离线应用开发
前端本地存储进阶:IndexedDB 封装与离线应用开发
文章目录
- 前端本地存储进阶:IndexedDB 封装与离线应用开发
- 前言
- 1. IndexedDB 基础概念
- 1.1 什么是 IndexedDB
- 1.2 基础 API 使用
- 2. 实战案例:智能 IndexedDB 封装库
- 2.1 高级封装库设计
- 2.2 实际应用示例
- 3. 离线应用架构设计
- 3.1 Service Worker 集成
- 3.2 离线数据同步策略
- 4. 完整实战:离线待办事项应用
- 4.1 应用架构
- 5. 性能优化与最佳实践
- 5.1 性能优化策略
- 5.2 错误处理与恢复
- 5.3 数据迁移与版本管理
- 6. 总结
- 关键要点:
前言
在现代前端开发中,数据的本地存储和离线应用支持变得越来越重要。从简单的 localStorage 到功能强大的 IndexedDB,浏览器为我们提供了丰富的本地存储解决方案。本文将深入探讨 IndexedDB 的高级用法,包括封装库的设计、离线应用的架构设计,以及实际项目中的最佳实践。
1. IndexedDB 基础概念
1.1 什么是 IndexedDB
IndexedDB 是一个事务型的数据库系统,用于在浏览器中存储大量结构化数据。与 localStorage 相比,它提供了更强大的功能:
- 大容量存储:可以存储大量数据(通常没有硬性限制)
- 异步操作:不会阻塞主线程
- 事务支持:保证数据操作的原子性
- 索引机制:支持高效的数据查询
- 多数据类型:支持字符串、数字、对象、二进制数据等
1.2 基础 API 使用
// 打开数据库
const request = indexedDB.open('MyDatabase', 1);request.onerror = function(event) {console.error('数据库打开失败');
};request.onsuccess = function(event) {const db = event.target.result;console.log('数据库打开成功');
};request.onupgradeneeded = function(event) {const db = event.target.result;// 创建对象存储空间if (!db.objectStoreNames.contains('users')) {const objectStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });// 创建索引objectStore.createIndex('name', 'name', { unique: false });objectStore.createIndex('email', 'email', { unique: true });}
};
2. 实战案例:智能 IndexedDB 封装库
2.1 高级封装库设计
让我们创建一个功能完整的 IndexedDB 封装库,提供类似 MongoDB 的链式调用接口:
class IndexedDBManager {constructor(databaseName, version = 1) {this.databaseName = databaseName;this.version = version;this.db = null;this.stores = new Map();}// 初始化数据库async init(storesConfig = {}) {return new Promise((resolve, reject) => {const request = indexedDB.open(this.databaseName, this.version);request.onerror = () => reject(request.error);request.onsuccess = () => {this.db = request.result;resolve(this);};request.onupgradeneeded = (event) => {const db = event.target.result;// 根据配置创建存储空间Object.entries(storesConfig).forEach(([storeName, config]) => {if (!db.objectStoreNames.contains(storeName)) {const store = db.createObjectStore(storeName, config.options);// 创建索引if (config.indexes) {Object.entries(config.indexes).forEach(([indexName, indexConfig]) => {store.createIndex(indexName, indexConfig.keyPath, indexConfig.options);});}}});};});}// 获取存储管理器store(storeName) {if (!this.stores.has(storeName)) {this.stores.set(storeName, new StoreManager(this.db, storeName));}return this.stores.get(storeName);}// 关闭数据库close() {if (this.db) {this.db.close();this.db = null;}}// 删除数据库async deleteDatabase() {return new Promise((resolve, reject) => {const request = indexedDB.deleteDatabase(this.databaseName);request.onsuccess = () => resolve();request.onerror = () => reject(request.error);});}
}// 存储管理器类
class StoreManager {constructor(db, storeName) {this.db = db;this.storeName = storeName;}// 添加数据async add(data, key) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readwrite');const store = transaction.objectStore(this.storeName);const request = key !== undefined ? store.add(data, key) : store.add(data);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}// 批量添加async addAll(items) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readwrite');const store = transaction.objectStore(this.storeName);const results = [];items.forEach(item => {const request = store.add(item);request.onsuccess = () => results.push(request.result);});transaction.oncomplete = () => resolve(results);transaction.onerror = () => reject(transaction.error);});}// 更新数据async update(data, key) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readwrite');const store = transaction.objectStore(this.storeName);const request = key !== undefined ? store.put(data, key) : store.put(data);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}// 删除数据async delete(key) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readwrite');const store = transaction.objectStore(this.storeName);const request = store.delete(key);request.onsuccess = () => resolve();request.onerror = () => reject(request.error);});}// 获取数据async get(key) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readonly');const store = transaction.objectStore(this.storeName);const request = store.get(key);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}// 获取所有数据async getAll(query = null, count = null) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readonly');const store = transaction.objectStore(this.storeName);const request = query !== null ? store.getAll(query, count) : store.getAll();request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}// 通过索引查询async getByIndex(indexName, value) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readonly');const store = transaction.objectStore(this.storeName);const index = store.index(indexName);const request = index.get(value);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}// 游标查询(支持复杂查询条件)async cursor(query = null, direction = 'next') {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readonly');const store = transaction.objectStore(this.storeName);const request = query !== null ? store.openCursor(query, direction) : store.openCursor();const results = [];request.onsuccess = (event) => {const cursor = event.target.result;if (cursor) {results.push(cursor.value);cursor.continue();} else {resolve(results);}};request.onerror = () => reject(request.error);});}// 统计数量async count(query = null) {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readonly');const store = transaction.objectStore(this.storeName);const request = query !== null ? store.count(query) : store.count();request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}// 清空存储async clear() {return new Promise((resolve, reject) => {const transaction = this.db.transaction([this.storeName], 'readwrite');const store = transaction.objectStore(this.storeName);const request = store.clear();request.onsuccess = () => resolve();request.onerror = () => reject(request.error);});}
}
2.2 实际应用示例
// 初始化数据库
const dbManager = new IndexedDBManager('AppDatabase', 1);// 配置存储空间
const storesConfig = {users: {options: { keyPath: 'id', autoIncrement: true },indexes: {name: { keyPath: 'name', options: { unique: false } },email: { keyPath: 'email', options: { unique: true } },age: { keyPath: 'age', options: { unique: false } }}},products: {options: { keyPath: 'sku' },indexes: {name: { keyPath: 'name', options: { unique: false } },category: { keyPath: 'category', options: { unique: false } },price: { keyPath: 'price', options: { unique: false } }}},cache: {options: { keyPath: 'key' },indexes: {timestamp: { keyPath: 'timestamp', options: { unique: false } }}}
};// 初始化数据库
await dbManager.init(storesConfig);// 使用示例
const usersStore = dbManager.store('users');// 添加用户
const userId = await usersStore.add({name: '张三',email: 'zhangsan@example.com',age: 25,createdAt: new Date()
});// 批量添加
await usersStore.addAll([{ name: '李四', email: 'lisi@example.com', age: 30 },{ name: '王五', email: 'wangwu@example.com', age: 28 }
]);// 查询用户
const user = await usersStore.get(userId);
const userByEmail = await usersStore.getByIndex('email', 'zhangsan@example.com');// 更新用户
await usersStore.update({id: userId,name: '张三',email: 'zhangsan@example.com',age: 26,updatedAt: new Date()
});// 删除用户
await usersStore.delete(userId);// 获取所有用户
const allUsers = await usersStore.getAll();// 条件查询(使用游标)
const youngUsers = await usersStore.cursor(IDBKeyRange.upperBound(30), 'age');// 统计数量
const userCount = await usersStore.count();
3. 离线应用架构设计
3.1 Service Worker 集成
// service-worker.js
const CACHE_NAME = 'app-v1';
const urlsToCache = ['/','/styles/main.css','/scripts/app.js','/offline.html'
];// 安装事件
self.addEventListener('install', event => {event.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(urlsToCache)));
});// 激活事件
self.addEventListener('activate', event => {event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (cacheName !== CACHE_NAME) {return caches.delete(cacheName);}}));}));
});// 拦截网络请求
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(response => {// 如果有缓存,直接返回if (response) {return response;}// 克隆请求const fetchRequest = event.request.clone();return fetch(fetchRequest).then(response => {// 检查是否有效响应if (!response || response.status !== 200 || response.type !== 'basic') {return response;}// 克隆响应const responseToCache = response.clone();caches.open(CACHE_NAME).then(cache => {cache.put(event.request, responseToCache);});return response;});}).catch(() => {// 离线时返回离线页面return caches.match('/offline.html');}));
});
3.2 离线数据同步策略
class OfflineSyncManager {constructor(dbManager) {this.dbManager = dbManager;this.syncQueue = [];this.isOnline = navigator.onLine;this.syncInProgress = false;this.setupEventListeners();}setupEventListeners() {window.addEventListener('online', () => {this.isOnline = true;this.startSync();});window.addEventListener('offline', () => {this.isOnline = false;});}// 添加同步任务addSyncTask(task) {this.syncQueue.push({id: Date.now() + Math.random(),type: task.type,data: task.data,timestamp: new Date(),status: 'pending'});if (this.isOnline) {this.startSync();}}// 开始同步async startSync() {if (this.syncInProgress || !this.isOnline) {return;}this.syncInProgress = true;try {const pendingTasks = this.syncQueue.filter(task => task.status === 'pending');for (const task of pendingTasks) {try {await this.processSyncTask(task);task.status = 'completed';} catch (error) {task.status = 'failed';task.error = error.message;console.error('同步任务失败:', error);}}// 清理已完成的任务this.syncQueue = this.syncQueue.filter(task => task.status !== 'completed');} finally {this.syncInProgress = false;}}// 处理同步任务async processSyncTask(task) {switch (task.type) {case 'create':return await this.syncCreate(task.data);case 'update':return await this.syncUpdate(task.data);case 'delete':return await this.syncDelete(task.data);default:throw new Error(`未知的同步类型: ${task.type}`);}}// 同步创建操作async syncCreate(data) {// 发送到服务器const response = await fetch('/api/create', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(data)});if (!response.ok) {throw new Error('创建同步失败');}const result = await response.json();// 更新本地数据const store = this.dbManager.store(data.storeName);await store.update({ ...data, id: result.id, synced: true });}// 同步更新操作async syncUpdate(data) {const response = await fetch(`/api/update/${data.id}`, {method: 'PUT',headers: {'Content-Type': 'application/json'},body: JSON.stringify(data)});if (!response.ok) {throw new Error('更新同步失败');}// 更新本地数据const store = this.dbManager.store(data.storeName);await store.update({ ...data, synced: true });}// 同步删除操作async syncDelete(data) {const response = await fetch(`/api/delete/${data.id}`, {method: 'DELETE'});if (!response.ok) {throw new Error('删除同步失败');}// 删除本地数据const store = this.dbManager.store(data.storeName);await store.delete(data.id);}// 获取同步状态getSyncStatus() {const total = this.syncQueue.length;const completed = this.syncQueue.filter(task => task.status === 'completed').length;const failed = this.syncQueue.filter(task => task.status === 'failed').length;const pending = this.syncQueue.filter(task => task.status === 'pending').length;return {total,completed,failed,pending,isOnline: this.isOnline,isSyncing: this.syncInProgress};}
}
4. 完整实战:离线待办事项应用
4.1 应用架构
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>离线待办事项应用</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;padding: 20px;}.container {max-width: 800px;margin: 0 auto;background: white;border-radius: 20px;box-shadow: 0 20px 40px rgba(0,0,0,0.1);overflow: hidden;}.header {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;padding: 30px;text-align: center;}.header h1 {font-size: 2.5rem;margin-bottom: 10px;}.status-bar {display: flex;justify-content: space-between;align-items: center;background: #f8f9fa;padding: 15px 30px;border-bottom: 1px solid #e9ecef;}.status-indicator {display: flex;align-items: center;gap: 10px;}.status-dot {width: 12px;height: 12px;border-radius: 50%;background: #dc3545;transition: background 0.3s ease;}.status-dot.online {background: #28a745;}.sync-status {font-size: 14px;color: #6c757d;}.add-todo {padding: 30px;border-bottom: 1px solid #e9ecef;}.add-todo-form {display: flex;gap: 15px;margin-bottom: 20px;}.todo-input {flex: 1;padding: 15px;border: 2px solid #e9ecef;border-radius: 10px;font-size: 16px;transition: border-color 0.3s ease;}.todo-input:focus {outline: none;border-color: #667eea;}.add-btn {padding: 15px 30px;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;border: none;border-radius: 10px;font-size: 16px;cursor: pointer;transition: transform 0.3s ease;}.add-btn:hover {transform: translateY(-2px);}.todo-list {padding: 30px;max-height: 500px;overflow-y: auto;}.todo-item {display: flex;align-items: center;padding: 20px;margin-bottom: 15px;background: #f8f9fa;border-radius: 10px;border-left: 4px solid #667eea;transition: all 0.3s ease;}.todo-item:hover {transform: translateX(5px);box-shadow: 0 5px 15px rgba(0,0,0,0.1);}.todo-item.completed {opacity: 0.7;border-left-color: #28a745;}.todo-item.syncing {border-left-color: #ffc107;}.todo-checkbox {width: 20px;height: 20px;margin-right: 15px;cursor: pointer;}.todo-content {flex: 1;font-size: 16px;}.todo-item.completed .todo-content {text-decoration: line-through;color: #6c757d;}.todo-actions {display: flex;gap: 10px;}.todo-btn {padding: 8px 12px;border: none;border-radius: 5px;font-size: 12px;cursor: pointer;transition: all 0.3s ease;}.edit-btn {background: #17a2b8;color: white;}.delete-btn {background: #dc3545;color: white;}.sync-btn {background: #ffc107;color: #212529;}.empty-state {text-align: center;padding: 60px 20px;color: #6c757d;}.empty-state h3 {font-size: 1.5rem;margin-bottom: 10px;}.sync-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0,0,0,0.5);display: none;align-items: center;justify-content: center;z-index: 1000;}.sync-modal {background: white;padding: 30px;border-radius: 10px;text-align: center;max-width: 400px;}.sync-spinner {width: 40px;height: 40px;border: 4px solid #f3f3f3;border-top: 4px solid #667eea;border-radius: 50%;animation: spin 1s linear infinite;margin: 0 auto 20px;}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}@media (max-width: 768px) {.add-todo-form {flex-direction: column;}.todo-item {flex-direction: column;align-items: flex-start;gap: 15px;}.todo-actions {align-self: flex-end;}}</style>
</head>
<body><div class="container"><div class="header"><h1>📋 离线待办事项</h1><p>支持离线使用的智能待办事项管理应用</p></div><div class="status-bar"><div class="status-indicator"><div id="statusDot" class="status-dot"></div><span id="statusText">离线</span></div><div class="sync-status" id="syncStatus">同步队列: 0</div></div><div class="add-todo"><div class="add-todo-form"><input type="text" id="todoInput" class="todo-input" placeholder="添加新的待办事项..." /><button onclick="addTodo()" class="add-btn">添加</button></div></div><div class="todo-list" id="todoList"><div class="empty-state"><h3>暂无待办事项</h3><p>添加你的第一个待办事项开始管理任务吧!</p></div></div></div><div class="sync-overlay" id="syncOverlay"><div class="sync-modal"><div class="sync-spinner"></div><h3>正在同步数据...</h3><p>请稍候,正在将本地数据同步到服务器</p></div></div><script>// 应用主类class TodoApp {constructor() {this.dbManager = null;this.syncManager = null;this.todos = [];this.init();}async init() {try {// 初始化数据库this.dbManager = new IndexedDBManager('TodoApp', 1);await this.dbManager.init({todos: {options: { keyPath: 'id', autoIncrement: true },indexes: {completed: { keyPath: 'completed', options: { unique: false } },createdAt: { keyPath: 'createdAt', options: { unique: false } },synced: { keyPath: 'synced', options: { unique: false } }}},syncQueue: {options: { keyPath: 'id', autoIncrement: true },indexes: {status: { keyPath: 'status', options: { unique: false } },timestamp: { keyPath: 'timestamp', options: { unique: false } }}}});// 初始化同步管理器this.syncManager = new OfflineSyncManager(this.dbManager);// 加载待办事项await this.loadTodos();// 设置事件监听this.setupEventListeners();// 更新UIthis.updateUI();this.updateStatus();console.log('应用初始化完成');} catch (error) {console.error('应用初始化失败:', error);}}setupEventListeners() {// 网络状态变化window.addEventListener('online', () => {this.updateStatus();this.syncManager.startSync();});window.addEventListener('offline', () => {this.updateStatus();});// 输入框回车事件document.getElementById('todoInput').addEventListener('keypress', (e) => {if (e.key === 'Enter') {this.addTodo();}});}async loadTodos() {try {const todosStore = this.dbManager.store('todos');this.todos = await todosStore.getAll();} catch (error) {console.error('加载待办事项失败:', error);this.todos = [];}}async addTodo() {const input = document.getElementById('todoInput');const content = input.value.trim();if (!content) {return;}try {const todo = {content: content,completed: false,createdAt: new Date(),updatedAt: new Date(),synced: false};// 添加到数据库const todosStore = this.dbManager.store('todos');const id = await todosStore.add(todo);todo.id = id;// 添加到本地列表this.todos.push(todo);// 添加到同步队列this.syncManager.addSyncTask({type: 'create',data: { ...todo, storeName: 'todos' }});// 清空输入框input.value = '';// 更新UIthis.updateUI();this.updateStatus();console.log('待办事项添加成功');} catch (error) {console.error('添加待办事项失败:', error);}}async toggleTodo(id) {try {const todo = this.todos.find(t => t.id === id);if (!todo) {return;}todo.completed = !todo.completed;todo.updatedAt = new Date();todo.synced = false;// 更新数据库const todosStore = this.dbManager.store('todos');await todosStore.update(todo);// 添加到同步队列this.syncManager.addSyncTask({type: 'update',data: { ...todo, storeName: 'todos' }});// 更新UIthis.updateUI();this.updateStatus();} catch (error) {console.error('切换待办事项状态失败:', error);}}async deleteTodo(id) {try {// 从本地列表删除this.todos = this.todos.filter(t => t.id !== id);// 从数据库删除const todosStore = this.dbManager.store('todos');await todosStore.delete(id);// 添加到同步队列this.syncManager.addSyncTask({type: 'delete',data: { id: id, storeName: 'todos' }});// 更新UIthis.updateUI();this.updateStatus();} catch (error) {console.error('删除待办事项失败:', error);}}updateUI() {const todoList = document.getElementById('todoList');if (this.todos.length === 0) {todoList.innerHTML = `<div class="empty-state"><h3>暂无待办事项</h3><p>添加你的第一个待办事项开始管理任务吧!</p></div>`;return;}todoList.innerHTML = this.todos.map(todo => `<div class="todo-item ${todo.completed ? 'completed' : ''} ${!todo.synced ? 'syncing' : ''}"><input type="checkbox" class="todo-checkbox" ${todo.completed ? 'checked' : ''} onchange="todoApp.toggleTodo(${todo.id})"><div class="todo-content">${todo.content}</div><div class="todo-actions"><button class="todo-btn delete-btn" onclick="todoApp.deleteTodo(${todo.id})">删除</button></div></div>`).join('');}updateStatus() {const isOnline = navigator.onLine;const statusDot = document.getElementById('statusDot');const statusText = document.getElementById('statusText');const syncStatus = document.getElementById('syncStatus');if (isOnline) {statusDot.classList.add('online');statusText.textContent = '在线';} else {statusDot.classList.remove('online');statusText.textContent = '离线';}const syncStatusData = this.syncManager.getSyncStatus();syncStatus.textContent = `同步队列: ${syncStatusData.pending}`;}}// 简化版的 IndexedDBManager 和 OfflineSyncManager 实现// (这里使用之前定义的完整实现)// 全局应用实例let todoApp;// 页面加载完成后初始化应用document.addEventListener('DOMContentLoaded', () => {todoApp = new TodoApp();});// 全局函数供HTML调用function addTodo() {todoApp.addTodo();}</script>
</body>
</html>
5. 性能优化与最佳实践
5.1 性能优化策略
class PerformanceOptimizer {constructor(dbManager) {this.dbManager = dbManager;this.queryCache = new Map();this.cacheTimeout = 5 * 60 * 1000; // 5分钟缓存}// 查询缓存async cachedQuery(storeName, query, ttl = this.cacheTimeout) {const cacheKey = `${storeName}:${JSON.stringify(query)}`;const cached = this.queryCache.get(cacheKey);if (cached && Date.now() - cached.timestamp < ttl) {return cached.data;}const store = this.dbManager.store(storeName);const result = await store.getAll(query);this.queryCache.set(cacheKey, {data: result,timestamp: Date.now()});return result;}// 批量操作优化async batchOperation(storeName, operations) {const store = this.dbManager.store(storeName);const results = [];// 使用事务进行批量操作const transaction = this.dbManager.db.transaction([storeName], 'readwrite');const store = transaction.objectStore(storeName);for (const operation of operations) {try {let request;switch (operation.type) {case 'add':request = store.add(operation.data);break;case 'update':request = store.put(operation.data);break;case 'delete':request = store.delete(operation.key);break;}request.onsuccess = () => {results.push({ success: true, result: request.result });};request.onerror = () => {results.push({ success: false, error: request.error });};} catch (error) {results.push({ success: false, error: error.message });}}return new Promise((resolve) => {transaction.oncomplete = () => resolve(results);transaction.onerror = () => resolve(results);});}// 清理过期缓存cleanupCache() {const now = Date.now();for (const [key, cached] of this.queryCache.entries()) {if (now - cached.timestamp > this.cacheTimeout) {this.queryCache.delete(key);}}}
}
5.2 错误处理与恢复
class ErrorHandler {constructor() {this.errorLog = [];this.maxErrors = 100;}logError(error, context = {}) {const errorEntry = {timestamp: new Date(),message: error.message,stack: error.stack,context: context,userAgent: navigator.userAgent};this.errorLog.push(errorEntry);// 限制错误日志数量if (this.errorLog.length > this.maxErrors) {this.errorLog.shift();}console.error('IndexedDB Error:', errorEntry);}// 数据库操作重试机制async retryOperation(operation, maxRetries = 3, delay = 1000) {let lastError;for (let attempt = 1; attempt <= maxRetries; attempt++) {try {return await operation();} catch (error) {lastError = error;this.logError(error, { attempt, maxRetries });if (attempt < maxRetries) {await this.delay(delay * attempt); // 指数退避}}}throw lastError;}delay(ms) {return new Promise(resolve => setTimeout(resolve, ms));}// 数据库恢复async recoverDatabase(dbManager) {try {// 尝试关闭现有连接dbManager.close();// 等待一段时间后重新初始化await this.delay(2000);// 重新初始化数据库await dbManager.init();console.log('数据库恢复成功');return true;} catch (error) {this.logError(error, { recovery: true });return false;}}
}
5.3 数据迁移与版本管理
class DatabaseMigration {constructor(dbManager) {this.dbManager = dbManager;this.migrations = new Map();}// 注册迁移脚本registerMigration(version, migration) {this.migrations.set(version, migration);}// 执行迁移async migrate(currentVersion, targetVersion) {const versions = Array.from(this.migrations.keys()).sort((a, b) => a - b);for (const version of versions) {if (version > currentVersion && version <= targetVersion) {console.log(`执行迁移: ${version}`);await this.migrations.get(version)(this.dbManager);}}}
}// 使用示例
const migration = new DatabaseMigration(dbManager);// 注册迁移脚本
migration.registerMigration(2, async (dbManager) => {// 版本2:添加新字段const db = dbManager.db;const transaction = db.transaction(['users'], 'readwrite');const store = transaction.objectStore('users');// 为所有用户添加创建时间字段const request = store.openCursor();request.onsuccess = (event) => {const cursor = event.target.result;if (cursor) {const user = cursor.value;user.createdAt = user.createdAt || new Date();cursor.update(user);cursor.continue();}};
});migration.registerMigration(3, async (dbManager) => {// 版本3:创建新索引const db = dbManager.db;const transaction = db.transaction(['users'], 'readwrite');const store = transaction.objectStore('users');if (!store.indexNames.contains('status')) {store.createIndex('status', 'status', { unique: false });}
});
6. 总结
本文深入探讨了 IndexedDB 的高级用法和离线应用开发的最佳实践。通过完整的封装库设计、离线同步策略、性能优化和错误处理机制,我们构建了一个功能强大的前端本地存储解决方案。
关键要点:
-
IndexedDB 封装:通过面向对象的封装,简化数据库操作,提供类似 MongoDB 的链式调用接口。
-
离线同步:实现智能的离线数据同步机制,确保数据的一致性和完整性。
-
性能优化:使用缓存、批量操作、索引优化等技术提升应用性能。
-
错误处理:完善的错误处理和恢复机制,提高应用的稳定性。
-
实际应用:通过完整的待办事项应用案例,展示了理论知识的实际应用。
这些技术和最佳实践可以帮助你构建更加强大、可靠的离线 Web 应用,提供更好的用户体验。在实际项目中,还需要根据具体需求进行调整和优化,但本文提供的基础架构和思路可以作为很好的起点。
