3 分钟掌握前端 IndexedDB 高效用法,告别本地存储焦虑

IndexedDB 是浏览器提供的客户端数据库,可以存储大量结构化数据,支持事务操作,是 Web 应用离线存储的重要技术。

1. IndexedDB 基础概念

核心特性

  • 异步操作:所有操作都是异步的,避免阻塞主线程
  • 事务支持:提供原子性保证
  • 键值对存储:以对象存储为单位
  • 索引支持:可创建索引提高查询效率
  • 大容量存储:比 localStorage 大得多

基本工作流程

javascript 复制代码
// 1. 打开数据库
const request = indexedDB.open('MyDatabase', 1);


// 2. 处理升级事件
request.onupgradeneeded = function(event) {
    const db = event.target.result;
    // 创建对象存储空间
    if (!db.objectStoreNames.contains('users')) {
        const objectStore = db.createObjectStore('users', { keyPath: 'id' });
        objectStore.createIndex('name', 'name', { unique: false });
    }
};


// 3. 处理成功/失败
request.onsuccess = function(event) {
    const db = event.target.result;
    // 进行数据操作
};


request.onerror = function(event) {
    console.error('数据库打开失败:', event.target.error);
};

2. 封装 IndexedDB 工具类

javascript 复制代码
class IndexedDBHelper {
    constructor(dbName, version = 1) {
        this.dbName = dbName;
        this.version = version;
        this.db = null;
    }


    // 打开数据库
    open() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.dbName, this.version);
            
            request.onerror = () => reject(request.error);
            request.onsuccess = () => {
                this.db = request.result;
                resolve(this.db);
            };
            
            // 数据库升级
            request.onupgradeneeded = (event) => {
                this.db = event.target.result;
                this.setupSchema(event);
            };
        });
    }


    // 创建数据库结构
    setupSchema(event) {
        const db = event.target.result;
        
        // 创建用户表
        if (!db.objectStoreNames.contains('users')) {
            const userStore = db.createObjectStore('users', { keyPath: 'id' });
            userStore.createIndex('email', 'email', { unique: true });
            userStore.createIndex('name', 'name', { unique: false });
        }
        
        // 创建缓存表
        if (!db.objectStoreNames.contains('cache')) {
            const cacheStore = db.createObjectStore('cache', { keyPath: 'key' });
            cacheStore.createIndex('expireTime', 'expireTime', { unique: false });
        }
    }


    // 添加数据
    add(storeName, data) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([storeName], 'readwrite');
            const store = transaction.objectStore(storeName);
            const request = store.add(data);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }


    // 更新数据
    update(storeName, data) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([storeName], 'readwrite');
            const store = transaction.objectStore(storeName);
            const request = store.put(data);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }


    // 获取数据
    get(storeName, key) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([storeName], 'readonly');
            const store = transaction.objectStore(storeName);
            const request = store.get(key);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }


    // 查询数据
    getAll(storeName) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([storeName], 'readonly');
            const store = transaction.objectStore(storeName);
            const request = store.getAll();
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }


    // 删除数据
    delete(storeName, key) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([storeName], 'readwrite');
            const store = transaction.objectStore(storeName);
            const request = store.delete(key);
            
            request.onsuccess = () => resolve();
            request.onerror = () => reject(request.error);
        });
    }


    // 清空表
    clear(storeName) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([storeName], 'readwrite');
            const store = transaction.objectStore(storeName);
            const request = store.clear();
            
            request.onsuccess = () => resolve();
            request.onerror = () => reject(request.error);
        });
    }
}

3. 实际应用示例

用户信息本地缓存

javascript 复制代码
// 使用示例
const db = new IndexedDBHelper('UserApp', 1);


// 初始化数据库
async function initDB() {
    try {
        await db.open();
        console.log('数据库初始化成功');
    } catch (error) {
        console.error('数据库初始化失败:', error);
    }
}


// 缓存用户数据
async function cacheUserInfo(userInfo) {
    try {
        await db.add('users', userInfo);
        console.log('用户信息缓存成功');
    } catch (error) {
        console.error('缓存失败:', error);
    }
}


// 获取用户信息
async function getUserInfo(userId) {
    try {
        const user = await db.get('users', userId);
        return user;
    } catch (error) {
        console.error('获取用户信息失败:', error);
        return null;
    }
}


// 搜索用户
async function searchUsers(searchTerm) {
    try {
        const allUsers = await db.getAll('users');
        return allUsers.filter(user => 
            user.name.includes(searchTerm) || user.email.includes(searchTerm)
        );
    } catch (error) {
        console.error('搜索用户失败:', error);
        return [];
    }
}

数据缓存管理器

javascript 复制代码
class CacheManager {
    constructor() {
        this.db = new IndexedDBHelper('CacheDB', 1);
        this.init();
    }


    async init() {
        await this.db.open();
    }


    // 设置缓存(带过期时间)
    async set(key, value, expire = 3600000) { // 默认1小时过期
        const cacheData = {
            key,
            value,
            createTime: Date.now(),
            expireTime: Date.now() + expire
        };
        await this.db.add('cache', cacheData);
    }


    // 获取缓存
    async get(key) {
        const cacheData = await this.db.get('cache', key);
        if (!cacheData) return null;


        // 检查是否过期
        if (Date.now() > cacheData.expireTime) {
            await this.remove(key);
            return null;
        }


        return cacheData.value;
    }


    // 删除缓存
    async remove(key) {
        await this.db.delete('cache', key);
    }


    // 清理过期缓存
    async cleanExpired() {
        const allCache = await this.db.getAll('cache');
        const now = Date.now();
        const expiredKeys = allCache
            .filter(item => item.expireTime < now)
            .map(item => item.key);


        for (const key of expiredKeys) {
            await this.remove(key);
        }
    }
}


// 使用示例
const cache = new CacheManager();


// 设置缓存
await cache.set('api_data', { data: 'some data' }, 3600000);


// 获取缓存
const cachedData = await cache.get('api_data');

4. 性能优化技巧

批量操作优化

javascript 复制代码
// 批量添加数据
async function batchAdd(storeName, dataArray) {
    const transaction = this.db.transaction([storeName], 'readwrite');
    const store = transaction.objectStore(storeName);
    
    const promises = dataArray.map(data => {
        return new Promise((resolve, reject) => {
            const request = store.add(data);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    });
    
    return Promise.all(promises);
}


// 批量更新
async function batchUpdate(storeName, dataArray) {
    const transaction = this.db.transaction([storeName], 'readwrite');
    const store = transaction.objectStore(storeName);
    
    const promises = dataArray.map(data => {
        return new Promise((resolve, reject) => {
            const request = store.put(data);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    });
    
    return Promise.all(promises);
}

索引优化

javascript 复制代码
// 创建高效的索引
setupSchema(event) {
    const db = event.target.result;
    
    // 为常用查询字段创建索引
    if (!db.objectStoreNames.contains('products')) {
        const productStore = db.createObjectStore('products', { keyPath: 'id' });
        productStore.createIndex('category', 'category', { unique: false });
        productStore.createIndex('price', 'price', { unique: false });
        productStore.createIndex('createdAt', 'createdAt', { unique: false });
        productStore.createIndex('searchIndex', ['name', 'description'], { unique: false });
    }
}


// 使用索引进行高效查询
async function searchProducts(category, minPrice) {
    const transaction = this.db.transaction(['products'], 'readonly');
    const store = transaction.objectStore('products');
    const categoryIndex = store.index('category');
    
    // 范围查询
    const range = IDBKeyRange.bound(minPrice, Infinity);
    const priceIndex = store.index('price');
    
    // 注意:实际使用时需要更复杂的查询逻辑
    const request = categoryIndex.getAll();
    return new Promise((resolve, reject) => {
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

5. 错误处理和最佳实践

完整的错误处理

javascript 复制代码
class RobustIndexedDB {
    constructor(dbName, version = 1) {
        this.dbName = dbName;
        this.version = version;
        this.db = null;
        this.isSupported = this.checkSupport();
    }


    checkSupport() {
        return !!window.indexedDB;
    }


    async safeOpen() {
        if (!this.isSupported) {
            throw new Error('IndexedDB not supported');
        }


        try {
            const db = await this.open();
            return db;
        } catch (error) {
            console.error('Database open failed:', error);
            throw error;
        }
    }


    async handleTransaction(operation) {
        try {
            return await operation();
        } catch (error) {
            console.error('Transaction failed:', error);
            throw error;
        }
    }
}

版本管理

javascript 复制代码
// 数据库版本升级处理
request.onupgradeneeded = function(event) {
    const db = event.target.result;
    
    // 根据不同版本执行不同的升级逻辑
    switch (event.oldVersion) {
        case 0:
            // 第一次创建
            createInitialStructure(db);
            break;
        case 1:
            // 从版本1升级到版本2
            upgradeFromV1ToV2(db);
            break;
        default:
            // 其他版本处理
            break;
    }
};


function createInitialStructure(db) {
    const userStore = db.createObjectStore('users', { keyPath: 'id' });
    userStore.createIndex('email', 'email', { unique: true });
    userStore.createIndex('name', 'name', { unique: false });
}


function upgradeFromV1ToV2(db) {
    // 添加新字段
    if (db.objectStoreNames.contains('users')) {
        const userStore = db.transaction('users', 'readwrite').objectStore('users');
        // 可以在这里添加新索引或修改结构
    }
}

6. 使用建议

适用场景

  1. 大量数据存储:超过 5MB 的数据存储需求
  2. 离线应用:需要在没有网络时访问数据
  3. 复杂查询:需要通过索引进行高效查询
  4. 持久化缓存:需要长期保存用户数据

注意事项

  1. 异步编程:始终使用 Promise 或回调处理异步操作
  2. 事务管理:合理使用事务确保数据一致性
  3. 内存管理:及时关闭数据库连接,避免内存泄漏
  4. 兼容性检查:使用前检查浏览器支持情况
  5. 错误处理:完善的错误处理机制

监控和调试

javascript 复制代码
// 添加调试信息
class DebugIndexedDB extends IndexedDBHelper {
    constructor(dbName, version = 1) {
        super(dbName, version);
        this.debug = true;
    }


    async debugOperation(operationName, operation) {
        if (this.debug) {
            console.time(`[DB] ${operationName}`);
        }
        
        try {
            const result = await operation();
            if (this.debug) {
                console.timeEnd(`[DB] ${operationName}`);
            }
            return result;
        } catch (error) {
            if (this.debug) {
                console.timeEnd(`[DB] ${operationName}`);
                console.error(`[DB] ${operationName} failed:`, error);
            }
            throw error;
        }
    }
}

通过以上封装和最佳实践,可以在前端项目中高效、安全地使用 IndexedDB,为用户提供更好的离线体验和数据持久化能力。

相关推荐
wearegogog12312 小时前
基于 MATLAB 的卡尔曼滤波器实现,用于消除噪声并估算信号
前端·算法·matlab
Drawing stars12 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
品克缤12 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小二·12 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
小沐°12 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_4198540513 小时前
CSS动效
前端·javascript·css
烛阴13 小时前
3D字体TextGeometry
前端·webgl·three.js
桜吹雪13 小时前
markstream-vue实战踩坑笔记
前端
C_心欲无痕14 小时前
nginx - 实现域名跳转的几种方式
运维·前端·nginx
花哥码天下14 小时前
恢复网站console.log的脚本
前端·javascript·vue.js