前端本地存储进阶:IndexedDB 封装与离线应用开发
文章目录
- [前端本地存储进阶:IndexedDB 封装与离线应用开发](#前端本地存储进阶:IndexedDB 封装与离线应用开发)
-
- 前言
- [1. IndexedDB 基础概念](#1. IndexedDB 基础概念)
-
- [1.1 什么是 IndexedDB](#1.1 什么是 IndexedDB)
- [1.2 基础 API 使用](#1.2 基础 API 使用)
- [2. 实战案例:智能 IndexedDB 封装库](#2. 实战案例:智能 IndexedDB 封装库)
-
- [2.1 高级封装库设计](#2.1 高级封装库设计)
- [2.2 实际应用示例](#2.2 实际应用示例)
- [3. 离线应用架构设计](#3. 离线应用架构设计)
-
- [3.1 Service Worker 集成](#3.1 Service Worker 集成)
- [3.2 离线数据同步策略](#3.2 离线数据同步策略)
- [4. 完整实战:离线待办事项应用](#4. 完整实战:离线待办事项应用)
-
- [4.1 应用架构](#4.1 应用架构)
- [5. 性能优化与最佳实践](#5. 性能优化与最佳实践)
-
- [5.1 性能优化策略](#5.1 性能优化策略)
- [5.2 错误处理与恢复](#5.2 错误处理与恢复)
- [5.3 数据迁移与版本管理](#5.3 数据迁移与版本管理)
- [6. 总结](#6. 总结)
前言
在现代前端开发中,数据的本地存储和离线应用支持变得越来越重要。从简单的 localStorage 到功能强大的 IndexedDB,浏览器为我们提供了丰富的本地存储解决方案。本文将深入探讨 IndexedDB 的高级用法,包括封装库的设计、离线应用的架构设计,以及实际项目中的最佳实践。
1. IndexedDB 基础概念
1.1 什么是 IndexedDB
IndexedDB 是一个事务型的数据库系统,用于在浏览器中存储大量结构化数据。与 localStorage 相比,它提供了更强大的功能:
- 大容量存储:可以存储大量数据(通常没有硬性限制)
- 异步操作:不会阻塞主线程
- 事务支持:保证数据操作的原子性
- 索引机制:支持高效的数据查询
- 多数据类型:支持字符串、数字、对象、二进制数据等
1.2 基础 API 使用
javascript
// 打开数据库
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 的链式调用接口:
javascript
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 实际应用示例
javascript
// 初始化数据库
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 集成
javascript
// 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 离线数据同步策略
javascript
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 应用架构
html
<!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();
// 更新UI
this.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 = '';
// 更新UI
this.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' }
});
// 更新UI
this.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' }
});
// 更新UI
this.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 性能优化策略
javascript
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 错误处理与恢复
javascript
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 数据迁移与版本管理
javascript
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 应用,提供更好的用户体验。在实际项目中,还需要根据具体需求进行调整和优化,但本文提供的基础架构和思路可以作为很好的起点。