IndexedDB

IndexedDB概述

IndexedDB是浏览器提供的NoSQL数据库,用于在客户端存储大量结构化数据。与Web存储相比,IndexedDB支持更大的存储空间、索引查询和事务处理,适合离线应用和需要存储大量数据的Web应用。

东巴文(db-w.cn) 认为:IndexedDB是浏览器中的数据库引擎,为Web应用提供了强大的本地数据存储能力。

IndexedDB特点

优点

特点 说明
大容量 存储空间可达数百MB甚至更多
结构化存储 支持对象存储,可存储复杂对象
索引查询 支持创建索引,快速查询数据
事务支持 支持事务操作,保证数据一致性
异步API 不阻塞主线程,性能更好
同源策略 遵循同源策略,数据安全

IndexedDB与其他存储对比

特性 IndexedDB localStorage Cookie
存储大小 大(数百MB+) 小(5MB) 很小(4KB)
数据类型 对象、数组、Blob等 字符串 字符串
查询能力 支持索引查询
事务支持 支持 不支持 不支持
API类型 异步 同步 同步
性能

东巴文点评:IndexedDB适合存储大量结构化数据,如离线应用数据、用户数据缓存等。

基本概念

数据库(Database)

数据库是IndexedDB的最顶层容器,每个域名可以创建多个数据库。

对象存储(Object Store)

对象存储类似于关系数据库中的表,用于存储JavaScript对象。

索引(Index)

索引用于快速查询数据,可以在对象存储的属性上创建索引。

事务(Transaction)

事务用于保证数据操作的原子性、一致性和隔离性。

键(Key)

每个存储的对象必须有一个键,可以是自动生成的,也可以是对象属性。

打开数据库

打开或创建数据库

// 打开或创建数据库
const request = indexedDB.open('myDatabase', 1);

// 成功回调
request.onsuccess = function(event) {
    const db = event.target.result;
    console.log('数据库打开成功');
};

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

// 升级回调(创建或升级数据库时触发)
request.onupgradeneeded = function(event) {
    const db = event.target.result;
    console.log('数据库升级');
    
    // 创建对象存储
    if (!db.objectStoreNames.contains('users')) {
        const objectStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
        console.log('对象存储创建成功');
    }
};

东巴文点评onupgradeneeded事件在数据库首次创建或版本号升级时触发,是创建对象存储和索引的最佳时机。

数据库版本管理

// 打开版本2的数据库
const request = indexedDB.open('myDatabase', 2);

request.onupgradeneeded = function(event) {
    const db = event.target.result;
    
    // 版本1到版本2的升级
    if (event.oldVersion < 1) {
        // 创建users对象存储
        const userStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
        userStore.createIndex('email', 'email', { unique: true });
    }
    
    if (event.oldVersion < 2) {
        // 创建products对象存储
        const productStore = db.createObjectStore('products', { keyPath: 'id', autoIncrement: true });
        productStore.createIndex('name', 'name', { unique: false });
        productStore.createIndex('price', 'price', { unique: false });
    }
};

创建对象存储

基本创建

request.onupgradeneeded = function(event) {
    const db = event.target.result;
    
    // 创建对象存储,使用id作为键,自动递增
    const userStore = db.createObjectStore('users', { 
        keyPath: 'id', 
        autoIncrement: true 
    });
    
    // 创建对象存储,使用email作为键
    const emailStore = db.createObjectStore('usersByEmail', { 
        keyPath: 'email' 
    });
    
    // 创建对象存储,使用自动生成的键
    const autoStore = db.createObjectStore('autoId', { 
        autoIncrement: true 
    });
};

创建索引

request.onupgradeneeded = function(event) {
    const db = event.target.result;
    
    const userStore = db.createObjectStore('users', { 
        keyPath: 'id', 
        autoIncrement: true 
    });
    
    // 创建唯一索引
    userStore.createIndex('email', 'email', { unique: true });
    
    // 创建非唯一索引
    userStore.createIndex('name', 'name', { unique: false });
    
    // 创建复合索引
    userStore.createIndex('nameAge', ['name', 'age'], { unique: false });
};

索引选项

选项 说明
unique 是否唯一(true/false)
multiEntry 是否为数组的每个元素创建索引(true/false)

添加数据

使用add方法

function addData(db) {
    // 创建事务
    const transaction = db.transaction(['users'], 'readwrite');
    
    // 获取对象存储
    const objectStore = transaction.objectStore('users');
    
    // 添加数据
    const request = objectStore.add({
        name: '张三',
        email: 'zhangsan@example.com',
        age: 25
    });
    
    request.onsuccess = function(event) {
        console.log('数据添加成功,ID:', event.target.result);
    };
    
    request.onerror = function(event) {
        console.error('数据添加失败:', event.target.error);
    };
}

使用put方法

function putData(db) {
    const transaction = db.transaction(['users'], 'readwrite');
    const objectStore = transaction.objectStore('users');
    
    // put方法:如果键存在则更新,不存在则添加
    const request = objectStore.put({
        id: 1,
        name: '李四',
        email: 'lisi@example.com',
        age: 30
    });
    
    request.onsuccess = function(event) {
        console.log('数据添加或更新成功');
    };
}

东巴文点评add方法只能添加新数据,如果键已存在会报错;put方法既可以添加也可以更新。

读取数据

根据键读取

function getDataByKey(db, id) {
    const transaction = db.transaction(['users'], 'readonly');
    const objectStore = transaction.objectStore('users');
    
    const request = objectStore.get(id);
    
    request.onsuccess = function(event) {
        const user = event.target.result;
        if (user) {
            console.log('用户信息:', user);
        } else {
            console.log('未找到用户');
        }
    };
    
    request.onerror = function(event) {
        console.error('读取失败:', event.target.error);
    };
}

根据索引读取

function getDataByIndex(db, email) {
    const transaction = db.transaction(['users'], 'readonly');
    const objectStore = transaction.objectStore('users');
    
    // 获取索引
    const index = objectStore.index('email');
    
    // 根据索引查询
    const request = index.get(email);
    
    request.onsuccess = function(event) {
        const user = event.target.result;
        if (user) {
            console.log('用户信息:', user);
        } else {
            console.log('未找到用户');
        }
    };
}

读取所有数据

function getAllData(db) {
    const transaction = db.transaction(['users'], 'readonly');
    const objectStore = transaction.objectStore('users');
    
    const request = objectStore.getAll();
    
    request.onsuccess = function(event) {
        const users = event.target.result;
        console.log('所有用户:', users);
    };
}

使用游标遍历

function cursorData(db) {
    const transaction = db.transaction(['users'], 'readonly');
    const objectStore = transaction.objectStore('users');
    
    const request = objectStore.openCursor();
    
    request.onsuccess = function(event) {
        const cursor = event.target.result;
        
        if (cursor) {
            console.log('用户:', cursor.value);
            cursor.continue(); // 继续下一个
        } else {
            console.log('遍历完成');
        }
    };
}

更新数据

使用put方法

function updateData(db, id) {
    const transaction = db.transaction(['users'], 'readwrite');
    const objectStore = transaction.objectStore('users');
    
    // 先读取数据
    const getRequest = objectStore.get(id);
    
    getRequest.onsuccess = function(event) {
        const user = event.target.result;
        
        if (user) {
            // 修改数据
            user.name = '王五';
            user.age = 28;
            
            // 更新数据
            const putRequest = objectStore.put(user);
            
            putRequest.onsuccess = function(event) {
                console.log('数据更新成功');
            };
        } else {
            console.log('未找到用户');
        }
    };
}

使用游标更新

function updateByCursor(db) {
    const transaction = db.transaction(['users'], 'readwrite');
    const objectStore = transaction.objectStore('users');
    
    const request = objectStore.openCursor();
    
    request.onsuccess = function(event) {
        const cursor = event.target.result;
        
        if (cursor) {
            const user = cursor.value;
            
            // 修改数据
            user.age += 1;
            
            // 更新当前游标位置的数据
            cursor.update(user);
            
            cursor.continue();
        } else {
            console.log('更新完成');
        }
    };
}

删除数据

根据键删除

function deleteData(db, id) {
    const transaction = db.transaction(['users'], 'readwrite');
    const objectStore = transaction.objectStore('users');
    
    const request = objectStore.delete(id);
    
    request.onsuccess = function(event) {
        console.log('数据删除成功');
    };
    
    request.onerror = function(event) {
        console.error('删除失败:', event.target.error);
    };
}

使用游标删除

function deleteByCursor(db) {
    const transaction = db.transaction(['users'], 'readwrite');
    const objectStore = transaction.objectStore('users');
    
    const request = objectStore.openCursor();
    
    request.onsuccess = function(event) {
        const cursor = event.target.result;
        
        if (cursor) {
            // 删除当前游标位置的数据
            cursor.delete();
            cursor.continue();
        } else {
            console.log('删除完成');
        }
    };
}

清空对象存储

function clearStore(db) {
    const transaction = db.transaction(['users'], 'readwrite');
    const objectStore = transaction.objectStore('users');
    
    const request = objectStore.clear();
    
    request.onsuccess = function(event) {
        console.log('对象存储已清空');
    };
}

查询数据

范围查询

function rangeQuery(db) {
    const transaction = db.transaction(['users'], 'readonly');
    const objectStore = transaction.objectStore('users');
    const index = objectStore.index('age');
    
    // 创建范围:年龄在20到30之间
    const range = IDBKeyRange.bound(20, 30);
    
    const request = index.openCursor(range);
    
    request.onsuccess = function(event) {
        const cursor = event.target.result;
        
        if (cursor) {
            console.log('用户:', cursor.value);
            cursor.continue();
        } else {
            console.log('查询完成');
        }
    };
}

IDBKeyRange方法

方法 说明 示例
bound(lower, upper, lowerOpen, upperOpen) 范围查询 IDBKeyRange.bound(20, 30)
only(value) 精确匹配 IDBKeyRange.only(25)
lowerBound(lower, open) 大于等于 IDBKeyRange.lowerBound(20)
upperBound(upper, open) 小于等于 IDBKeyRange.upperBound(30)

排序

function sortData(db) {
    const transaction = db.transaction(['users'], 'readonly');
    const objectStore = transaction.objectStore('users');
    const index = objectStore.index('age');
    
    // 升序(默认)
    const request = index.openCursor();
    
    // 降序
    // const request = index.openCursor(null, 'prev');
    
    request.onsuccess = function(event) {
        const cursor = event.target.result;
        
        if (cursor) {
            console.log('年龄:', cursor.value.age);
            cursor.continue();
        }
    };
}

事务处理

事务模式

模式 说明
readonly 只读模式(默认)
readwrite 读写模式
versionchange 版本变更模式

事务事件

function transactionExample(db) {
    const transaction = db.transaction(['users'], 'readwrite');
    const objectStore = transaction.objectStore('users');
    
    // 事务完成
    transaction.oncomplete = function(event) {
        console.log('事务完成');
    };
    
    // 事务错误
    transaction.onerror = function(event) {
        console.error('事务错误:', event.target.error);
    };
    
    // 事务中止
    transaction.onabort = function(event) {
        console.log('事务中止');
    };
    
    // 添加多个数据
    objectStore.add({ name: '张三', age: 25 });
    objectStore.add({ name: '李四', age: 30 });
}

东巴文点评:事务是IndexedDB保证数据一致性的核心机制,所有数据操作都应该在事务中进行。

封装工具类

class IndexedDBUtil {
    constructor(dbName, version = 1) {
        this.dbName = dbName;
        this.version = version;
        this.db = null;
    }
    
    // 打开数据库
    open(storeConfig) {
        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) => {
                const db = event.target.result;
                
                // 创建对象存储
                storeConfig.forEach(config => {
                    if (!db.objectStoreNames.contains(config.name)) {
                        const store = db.createObjectStore(config.name, config.options);
                        
                        // 创建索引
                        if (config.indexes) {
                            config.indexes.forEach(index => {
                                store.createIndex(index.name, index.keyPath, index.options);
                            });
                        }
                    }
                });
            };
        });
    }
    
    // 添加数据
    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);
        });
    }
    
    // 更新数据
    put(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);
        });
    }
    
    // 通过索引查询
    getByIndex(storeName, indexName, value) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction([storeName], 'readonly');
            const store = transaction.objectStore(storeName);
            const index = store.index(indexName);
            const request = index.get(value);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    // 关闭数据库
    close() {
        if (this.db) {
            this.db.close();
            this.db = null;
        }
    }
}

// 使用示例
const dbUtil = new IndexedDBUtil('myDatabase', 1);

// 配置对象存储
const storeConfig = [
    {
        name: 'users',
        options: { keyPath: 'id', autoIncrement: true },
        indexes: [
            { name: 'email', keyPath: 'email', options: { unique: true } },
            { name: 'name', keyPath: 'name', options: { unique: false } }
        ]
    }
];

// 打开数据库
dbUtil.open(storeConfig).then(() => {
    console.log('数据库打开成功');
    
    // 添加数据
    return dbUtil.add('users', {
        name: '张三',
        email: 'zhangsan@example.com',
        age: 25
    });
}).then(id => {
    console.log('数据添加成功,ID:', id);
    
    // 获取数据
    return dbUtil.get('users', id);
}).then(user => {
    console.log('用户信息:', user);
});

综合示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IndexedDB示例 - 东巴文</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        
        .section {
            margin: 20px 0;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        
        .user-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            margin: 10px 0;
            background: #f5f5f5;
            border-radius: 3px;
        }
        
        input {
            padding: 8px;
            margin: 5px;
            border: 1px solid #ddd;
            border-radius: 3px;
        }
        
        button {
            padding: 8px 16px;
            margin: 5px;
            border: none;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border-radius: 3px;
            cursor: pointer;
        }
        
        button:hover {
            opacity: 0.9;
        }
        
        .stats {
            display: flex;
            gap: 20px;
            margin: 20px 0;
        }
        
        .stat-item {
            flex: 1;
            padding: 15px;
            background: #f5f5f5;
            border-radius: 5px;
            text-align: center;
        }
    </style>
</head>
<body>
    <h1>IndexedDB示例</h1>
    
    <div class="stats">
        <div class="stat-item">
            <h3>数据库状态</h3>
            <p id="dbStatus">未连接</p>
        </div>
        <div class="stat-item">
            <h3>用户数量</h3>
            <p id="userCount">0</p>
        </div>
    </div>
    
    <div class="section">
        <h2>添加用户</h2>
        <input type="text" id="nameInput" placeholder="姓名">
        <input type="email" id="emailInput" placeholder="邮箱">
        <input type="number" id="ageInput" placeholder="年龄">
        <button onclick="addUser()">添加用户</button>
    </div>
    
    <div class="section">
        <h2>用户列表</h2>
        <div id="userList"></div>
        <button onclick="loadUsers()">刷新列表</button>
        <button onclick="clearUsers()">清空所有</button>
    </div>
    
    <div class="section">
        <h2>搜索用户</h2>
        <input type="text" id="searchInput" placeholder="输入邮箱搜索">
        <button onclick="searchUser()">搜索</button>
        <div id="searchResult"></div>
    </div>
    
    <script>
        class IndexedDBUtil {
            constructor(dbName, version = 1) {
                this.dbName = dbName;
                this.version = version;
                this.db = null;
            }
            
            open(storeConfig) {
                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) => {
                        const db = event.target.result;
                        
                        storeConfig.forEach(config => {
                            if (!db.objectStoreNames.contains(config.name)) {
                                const store = db.createObjectStore(config.name, config.options);
                                
                                if (config.indexes) {
                                    config.indexes.forEach(index => {
                                        store.createIndex(index.name, index.keyPath, index.options);
                                    });
                                }
                            }
                        });
                    };
                });
            }
            
            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);
                });
            }
            
            put(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);
                });
            }
            
            getByIndex(storeName, indexName, value) {
                return new Promise((resolve, reject) => {
                    const transaction = this.db.transaction([storeName], 'readonly');
                    const store = transaction.objectStore(storeName);
                    const index = store.index(indexName);
                    const request = index.get(value);
                    
                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }
        }
        
        const dbUtil = new IndexedDBUtil('UserDatabase', 1);
        
        const storeConfig = [
            {
                name: 'users',
                options: { keyPath: 'id', autoIncrement: true },
                indexes: [
                    { name: 'email', keyPath: 'email', options: { unique: true } },
                    { name: 'name', keyPath: 'name', options: { unique: false } }
                ]
            }
        ];
        
        // 初始化数据库
        dbUtil.open(storeConfig).then(() => {
            document.getElementById('dbStatus').textContent = '已连接';
            loadUsers();
        }).catch(error => {
            console.error('数据库打开失败:', error);
            document.getElementById('dbStatus').textContent = '连接失败';
        });
        
        // 添加用户
        async function addUser() {
            const name = document.getElementById('nameInput').value.trim();
            const email = document.getElementById('emailInput').value.trim();
            const age = parseInt(document.getElementById('ageInput').value);
            
            if (!name || !email || !age) {
                alert('请填写完整信息');
                return;
            }
            
            try {
                await dbUtil.add('users', { name, email, age });
                alert('添加成功');
                
                // 清空输入框
                document.getElementById('nameInput').value = '';
                document.getElementById('emailInput').value = '';
                document.getElementById('ageInput').value = '';
                
                // 刷新列表
                loadUsers();
            } catch (error) {
                console.error('添加失败:', error);
                alert('添加失败: ' + error.message);
            }
        }
        
        // 加载用户列表
        async function loadUsers() {
            try {
                const users = await dbUtil.getAll('users');
                
                const userListEl = document.getElementById('userList');
                userListEl.innerHTML = users.map(user => `
                    <div class="user-item">
                        <span><strong>${user.name}</strong> (${user.email}) - ${user.age}岁</span>
                        <button onclick="deleteUser(${user.id})">删除</button>
                    </div>
                `).join('');
                
                document.getElementById('userCount').textContent = users.length;
            } catch (error) {
                console.error('加载失败:', error);
            }
        }
        
        // 删除用户
        async function deleteUser(id) {
            if (confirm('确定删除此用户?')) {
                try {
                    await dbUtil.delete('users', id);
                    alert('删除成功');
                    loadUsers();
                } catch (error) {
                    console.error('删除失败:', error);
                    alert('删除失败: ' + error.message);
                }
            }
        }
        
        // 清空所有用户
        async function clearUsers() {
            if (confirm('确定清空所有用户?')) {
                try {
                    await dbUtil.clear('users');
                    alert('清空成功');
                    loadUsers();
                } catch (error) {
                    console.error('清空失败:', error);
                    alert('清空失败: ' + error.message);
                }
            }
        }
        
        // 搜索用户
        async function searchUser() {
            const email = document.getElementById('searchInput').value.trim();
            
            if (!email) {
                alert('请输入邮箱');
                return;
            }
            
            try {
                const user = await dbUtil.getByIndex('users', 'email', email);
                
                const resultEl = document.getElementById('searchResult');
                
                if (user) {
                    resultEl.innerHTML = `
                        <div class="user-item">
                            <span><strong>${user.name}</strong> (${user.email}) - ${user.age}岁</span>
                        </div>
                    `;
                } else {
                    resultEl.innerHTML = '<p>未找到用户</p>';
                }
            } catch (error) {
                console.error('搜索失败:', error);
                alert('搜索失败: ' + error.message);
            }
        }
    </script>
</body>
</html>

最佳实践

1. 错误处理

// 推荐:始终处理错误
async function safeAdd(dbUtil, storeName, data) {
    try {
        const id = await dbUtil.add(storeName, data);
        console.log('添加成功:', id);
        return id;
    } catch (error) {
        console.error('添加失败:', error);
        throw error;
    }
}

// 不推荐:不处理错误
dbUtil.add('users', data).then(id => {
    console.log('添加成功:', id);
});

2. 数据验证

function validateUser(user) {
    if (!user.name || user.name.trim() === '') {
        throw new Error('姓名不能为空');
    }
    
    if (!user.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(user.email)) {
        throw new Error('邮箱格式不正确');
    }
    
    if (!user.age || user.age < 0 || user.age > 150) {
        throw new Error('年龄必须在0-150之间');
    }
    
    return true;
}

async function addUser(user) {
    validateUser(user);
    return await dbUtil.add('users', user);
}

3. 批量操作

async function batchAdd(users) {
    const transaction = dbUtil.db.transaction(['users'], 'readwrite');
    const store = transaction.objectStore('users');
    
    const promises = users.map(user => {
        return new Promise((resolve, reject) => {
            const request = store.add(user);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    });
    
    return Promise.all(promises);
}

学习检验

知识点测试

问题1:IndexedDB中用于创建对象存储的方法是?

A. createStore() B. createObjectStore() C. addObjectStore() D. newObjectStore()

<details> <summary>点击查看答案</summary>

答案:B

东巴文解释:在onupgradeneeded事件中,使用db.createObjectStore()方法创建对象存储。

</details>

问题2:IndexedDB的事务模式不包括以下哪种?

A. readonly B. readwrite C. versionchange D. readwriteall

<details> <summary>点击查看答案</summary>

答案:D

东巴文解释:IndexedDB支持三种事务模式:readonly(只读)、readwrite(读写)、versionchange(版本变更)。

</details>

实践任务

任务:创建一个IndexedDB数据库,存储书籍信息(书名、作者、ISBN、价格),实现添加、查询、删除功能。

<details> <summary>点击查看参考答案</summary>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>书籍管理</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        
        .book-item {
            padding: 15px;
            margin: 10px 0;
            background: #f5f5f5;
            border-radius: 5px;
        }
        
        input {
            padding: 8px;
            margin: 5px;
            border: 1px solid #ddd;
            border-radius: 3px;
        }
        
        button {
            padding: 8px 16px;
            margin: 5px;
            border: none;
            background: #667eea;
            color: white;
            border-radius: 3px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>书籍管理</h1>
    
    <div>
        <h2>添加书籍</h2>
        <input type="text" id="title" placeholder="书名">
        <input type="text" id="author" placeholder="作者">
        <input type="text" id="isbn" placeholder="ISBN">
        <input type="number" id="price" placeholder="价格">
        <button onclick="addBook()">添加</button>
    </div>
    
    <div>
        <h2>书籍列表</h2>
        <div id="bookList"></div>
    </div>
    
    <script>
        class BookDB {
            constructor() {
                this.db = null;
            }
            
            open() {
                return new Promise((resolve, reject) => {
                    const request = indexedDB.open('BookDatabase', 1);
                    
                    request.onerror = () => reject(request.error);
                    
                    request.onsuccess = () => {
                        this.db = request.result;
                        resolve(this.db);
                    };
                    
                    request.onupgradeneeded = (event) => {
                        const db = event.target.result;
                        const store = db.createObjectStore('books', { keyPath: 'isbn' });
                        store.createIndex('title', 'title', { unique: false });
                        store.createIndex('author', 'author', { unique: false });
                    };
                });
            }
            
            add(book) {
                return new Promise((resolve, reject) => {
                    const transaction = this.db.transaction(['books'], 'readwrite');
                    const store = transaction.objectStore('books');
                    const request = store.add(book);
                    
                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }
            
            getAll() {
                return new Promise((resolve, reject) => {
                    const transaction = this.db.transaction(['books'], 'readonly');
                    const store = transaction.objectStore('books');
                    const request = store.getAll();
                    
                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }
            
            delete(isbn) {
                return new Promise((resolve, reject) => {
                    const transaction = this.db.transaction(['books'], 'readwrite');
                    const store = transaction.objectStore('books');
                    const request = store.delete(isbn);
                    
                    request.onsuccess = () => resolve();
                    request.onerror = () => reject(request.error);
                });
            }
        }
        
        const bookDB = new BookDB();
        
        bookDB.open().then(() => {
            console.log('数据库打开成功');
            loadBooks();
        }).catch(error => {
            console.error('数据库打开失败:', error);
        });
        
        async function addBook() {
            const title = document.getElementById('title').value.trim();
            const author = document.getElementById('author').value.trim();
            const isbn = document.getElementById('isbn').value.trim();
            const price = parseFloat(document.getElementById('price').value);
            
            if (!title || !author || !isbn || !price) {
                alert('请填写完整信息');
                return;
            }
            
            try {
                await bookDB.add({ title, author, isbn, price });
                alert('添加成功');
                loadBooks();
            } catch (error) {
                console.error('添加失败:', error);
                alert('添加失败: ' + error.message);
            }
        }
        
        async function loadBooks() {
            try {
                const books = await bookDB.getAll();
                
                const listEl = document.getElementById('bookList');
                listEl.innerHTML = books.map(book => `
                    <div class="book-item">
                        <h3>${book.title}</h3>
                        <p>作者: ${book.author}</p>
                        <p>ISBN: ${book.isbn}</p>
                        <p>价格: ¥${book.price}</p>
                        <button onclick="deleteBook('${book.isbn}')">删除</button>
                    </div>
                `).join('');
            } catch (error) {
                console.error('加载失败:', error);
            }
        }
        
        async function deleteBook(isbn) {
            if (confirm('确定删除此书籍?')) {
                try {
                    await bookDB.delete(isbn);
                    alert('删除成功');
                    loadBooks();
                } catch (error) {
                    console.error('删除失败:', error);
                    alert('删除失败: ' + error.message);
                }
            }
        }
    </script>
</body>
</html>
</details>

东巴文(db-w.cn) - 让编程学习更有趣、更高效!