前端本地存储进阶:IndexedDB 封装与离线应用开发

前端本地存储进阶: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 的高级用法和离线应用开发的最佳实践。通过完整的封装库设计、离线同步策略、性能优化和错误处理机制,我们构建了一个功能强大的前端本地存储解决方案。

关键要点:

  1. IndexedDB 封装:通过面向对象的封装,简化数据库操作,提供类似 MongoDB 的链式调用接口。

  2. 离线同步:实现智能的离线数据同步机制,确保数据的一致性和完整性。

  3. 性能优化:使用缓存、批量操作、索引优化等技术提升应用性能。

  4. 错误处理:完善的错误处理和恢复机制,提高应用的稳定性。

  5. 实际应用:通过完整的待办事项应用案例,展示了理论知识的实际应用。

这些技术和最佳实践可以帮助你构建更加强大、可靠的离线 Web 应用,提供更好的用户体验。在实际项目中,还需要根据具体需求进行调整和优化,但本文提供的基础架构和思路可以作为很好的起点。

相关推荐
忍者扔飞镖2 小时前
欧服加载太慢了,咋整
前端·性能优化
鹏北海2 小时前
Vue 3 超强二维码识别:多区域/多尺度扫描 + 高级图像处理
前端·javascript·vue.js
Android疑难杂症2 小时前
一文讲清鸿蒙网络开发
前端·javascript·harmonyos
爱学习的程序媛2 小时前
【JavaScript基础】Null类型详解
前端·javascript
前端一课2 小时前
uniapp之WebView容器原理详解
前端
CryptoRzz2 小时前
DeepSeek印度股票数据源 Java 对接文档
前端·后端
网络点点滴3 小时前
watch监视-ref基本类型数据
前端·javascript·vue.js
西洼工作室3 小时前
前端接口安全与性能优化实战
前端·vue.js·安全·axios
大布布将军3 小时前
《前端九阴真经》
前端·javascript·经验分享·程序人生·前端框架·1024程序员节