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,为用户提供更好的离线体验和数据持久化能力。

相关推荐
_AaronWong5 小时前
基于 CropperJS 的图片编辑器实现
前端·vue.js·图片资源
默默地离开5 小时前
React Native 入门实战:样式、状态管理与网络请求全解析 (二)
前端·react native
Q_Q5110082855 小时前
python+springboot+vue的旅游门票信息系统web
前端·spring boot·python·django·flask·node.js·php
墨白曦煜5 小时前
快速学习Python(有其他语言基础)
前端·python·学习
FserSuN5 小时前
React 标准 SPA 项目 入门学习记录
前端·学习·react.js
YAY_tyy5 小时前
【Cesium 开发实战教程】第六篇:三维模型高级交互:点击查询、材质修改与动画控制
前端·javascript·3d·教程·cesium
蜡笔小电芯5 小时前
【HTML】 第一章:HTML 基础
前端·html
正义的大古5 小时前
OpenLayers地图交互 -- 章节十:拖拽平移交互详解
前端·javascript·vue.js·openlayers
JosieBook5 小时前
【SpringBoot】27 核心功能 - Web开发原理 - Spring MVC中的定制化原理
前端·spring boot·spring