前端 IndexedDB 详细教程
IndexedDB 是一个浏览器内置的 NoSQL 数据库系统,允许在客户端存储大量结构化数据,并支持高性能搜索。相比 localStorage,IndexedDB 更适合存储大量数据并提供更复杂的查询功能。
基本概念
- 数据库:每个源(协议+域名+端口)可以创建多个数据库
- 对象存储(Object Store):类似于数据库中的表
- 索引(Index):用于快速查找数据
- 事务(Transaction):所有操作必须在事务中执行
- 游标(Cursor):用于遍历对象存储中的数据
打开/创建数据库
const request = indexedDB.open('myDatabase', 1);request.onerror = function(event) {console.error("数据库打开失败:", event.target.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('customers')) {const store = db.createObjectStore('customers', { keyPath: 'id' });// 创建索引store.createIndex('name', 'name', { unique: false });store.createIndex('email', 'email', { unique: true });}
};
添加数据
function addCustomer(db, customer) {const transaction = db.transaction(['customers'], 'readwrite');const store = transaction.objectStore('customers');const request = store.add(customer);request.onsuccess = function() {console.log('数据添加成功');};request.onerror = function(event) {console.error('数据添加失败:', event.target.error);};
}// 使用示例
const db = /* 获取数据库实例 */;
addCustomer(db, {id: 1,name: '张三',email: 'zhangsan@example.com',age: 30
});
读取数据
通过主键读取
function getCustomer(db, id) {const transaction = db.transaction(['customers'], 'readonly');const store = transaction.objectStore('customers');const request = store.get(id);request.onsuccess = function() {const customer = request.result;if (customer) {console.log('找到客户:', customer);} else {console.log('未找到客户');}};request.onerror = function(event) {console.error('读取数据失败:', event.target.error);};
}
通过索引读取
function getCustomerByName(db, name) {const transaction = db.transaction(['customers'], 'readonly');const store = transaction.objectStore('customers');const index = store.index('name');const request = index.get(name);request.onsuccess = function() {const customer = request.result;console.log('找到客户:', customer);};request.onerror = function(event) {console.error('通过索引查询失败:', event.target.error);};
}
更新数据
function updateCustomer(db, customer) {const transaction = db.transaction(['customers'], 'readwrite');const store = transaction.objectStore('customers');const request = store.put(customer);request.onsuccess = function() {console.log('数据更新成功');};request.onerror = function(event) {console.error('数据更新失败:', event.target.error);};
}
删除数据
function deleteCustomer(db, id) {const transaction = db.transaction(['customers'], 'readwrite');const store = transaction.objectStore('customers');const request = store.delete(id);request.onsuccess = function() {console.log('数据删除成功');};request.onerror = function(event) {console.error('数据删除失败:', event.target.error);};
}
使用游标遍历数据
function getAllCustomers(db) {const transaction = db.transaction(['customers'], 'readonly');const store = transaction.objectStore('customers');const request = store.openCursor();const customers = [];request.onsuccess = function(event) {const cursor = event.target.result;if (cursor) {customers.push(cursor.value);cursor.continue();} else {console.log('所有客户:', customers);}};request.onerror = function(event) {console.error('遍历数据失败:', event.target.error);};
}
高级用法
使用索引范围查询
function getCustomersByAgeRange(db, min, max) {const transaction = db.transaction(['customers'], 'readonly');const store = transaction.objectStore('customers');// 假设我们创建了age索引const index = store.index('age');const range = IDBKeyRange.bound(min, max);const request = index.openCursor(range);const customers = [];request.onsuccess = function(event) {const cursor = event.target.result;if (cursor) {customers.push(cursor.value);cursor.continue();} else {console.log('年龄范围内的客户:', customers);}};
}
批量操作
function addMultipleCustomers(db, customers) {const transaction = db.transaction(['customers'], 'readwrite');const store = transaction.objectStore('customers');customers.forEach(customer => {store.add(customer);});transaction.oncomplete = function() {console.log('批量添加完成');};transaction.onerror = function(event) {console.error('批量操作失败:', event.target.error);};
}
数据库版本管理
// 升级数据库版本
function upgradeDB() {const request = indexedDB.open('myDatabase', 2); // 版本号增加request.onupgradeneeded = function(event) {const db = event.target.result;const oldVersion = event.oldVersion;const newVersion = event.newVersion;// 从版本1升级到版本2if (oldVersion < 1) {// 初始创建逻辑}if (oldVersion < 2) {// 版本2的变更if (!db.objectStoreNames.contains('orders')) {const store = db.createObjectStore('orders', { keyPath: 'orderId' });store.createIndex('customerId', 'customerId', { unique: false });}}};
}
最佳实践
- 错误处理:始终处理onerror事件
- 事务管理:合理使用事务,避免长时间持有事务
- 性能优化:对于大量数据操作,使用游标分批处理
- 内存管理:处理完数据后关闭游标和数据库连接
- 兼容性:检查浏览器支持情况
if (!window.indexedDB) {console.error("您的浏览器不支持IndexedDB");
}
封装示例
class IndexedDBWrapper {constructor(dbName, version) {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 = (event) => reject(event.target.error);request.onsuccess = (event) => {this.db = event.target.result;resolve(this.db);};request.onupgradeneeded = (event) => {const db = event.target.result;if (!db.objectStoreNames.contains('data')) {const store = db.createObjectStore('data', { keyPath: 'id' });store.createIndex('timestamp', 'timestamp', { 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 = (event) => reject(event.target.error);});}// 其他方法类似封装...
}// 使用示例
(async function() {const dbWrapper = new IndexedDBWrapper('myAppDB', 1);try {await dbWrapper.open();await dbWrapper.add('data', { id: 1, value: 'test', timestamp: Date.now() });console.log('操作成功');} catch (error) {console.error('操作失败:', error);}
})();
IndexedDB 提供了强大的客户端存储能力,适合需要离线功能或处理大量结构化数据的 Web 应用。通过合理使用,可以显著提升应用性能和用户体验。