理解IndexedDB的基本概念
IndexedDB是一种浏览器内置的数据库系统,允许在客户端存储大量结构化数据。它基于键值对存储,支持事务操作,适用于需要离线访问的Web应用。与传统LocalStorage不同,IndexedDB能处理更复杂的数据结构,存储容量更大(通常为浏览器剩余空间的50%)。
IndexedDB采用异步API设计,避免阻塞主线程。数据库操作通过请求和回调机制完成,开发者需熟悉事件驱动的编程模式。数据库按域隔离,不同网站无法访问彼此数据,确保安全性。每个数据库有唯一名称和版本号,版本升级时可修改对象存储结构。
创建或打开数据库连接
初始化IndexedDB需调用window.indexedDB.open()
方法,传入数据库名称和可选版本号。该方法返回一个IDBOpenDBRequest对象,需监听其成功、错误和升级事件。数据库不存在时会自动创建,版本号用于schema变更管理。
const request = indexedDB.open('MyDatabase', 1);
request.onerror = (event) => {
console.error('Database error:', event.target.error);
};
request.onsuccess = (event) => {
const db = event.target.result;
console.log('Database opened successfully');
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 在此初始化对象存储
};
定义对象存储和索引
在onupgradeneeded
回调中创建对象存储(类似SQL的表)。每个存储需指定名称和键路径,可选配置autoIncrement。通过createIndex()
方法创建索引可加速查询,索引需指定名称、键路径和唯一性约束。
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('books', {
keyPath: 'isbn',
autoIncrement: false
});
store.createIndex('by_author', 'author', { unique: false });
store.createIndex('by_title', 'title', { unique: true });
};
执行数据写入操作
所有写操作必须在事务中进行。创建事务时指定对象存储名称和访问模式(readwrite
或readonly
)。通过事务获取对象存储后,可调用add()
新增数据或put()
更新数据。批量操作时应重用同一事务以提高性能。
const transaction = db.transaction('books', 'readwrite');
const store = transaction.objectStore('books');
const book = {
isbn: '978-3-16-148410-0',
title: 'IndexedDB Guide',
author: 'John Doe'
};
const request = store.add(book);
request.onsuccess = () => {
console.log('Book added');
};
request.onerror = (event) => {
console.error('Add failed', event.target.error);
};
实现数据查询功能
查询数据通过get()
、getAll()
或索引完成。使用游标可遍历大量记录,通过continue()
控制迭代过程。复合查询需组合多个索引或使用KeyRange限定范围。
const transaction = db.transaction('books', 'readonly');
const store = transaction.objectStore('books');
const index = store.index('by_author');
const request = index.getAll('John Doe');
request.onsuccess = (event) => {
const books = event.target.result;
console.log('Found books:', books);
};
处理数据删除和更新
删除记录使用delete()
方法传入主键,清除整个存储用clear()
。更新操作通常通过put()
实现,注意需包含完整对象。批量更新时考虑使用Promise.all优化性能。
const transaction = db.transaction('books', 'readwrite');
const store = transaction.objectStore('books');
// 删除单条记录
store.delete('978-3-16-148410-0');
// 更新记录
const updatedBook = {
isbn: '978-3-16-148410-0',
title: 'Advanced IndexedDB',
author: 'John Doe'
};
store.put(updatedBook);
管理数据库版本和迁移
修改数据库结构需递增版本号。在onupgradeneeded
中比较新旧版本,执行创建存储、添加索引等迁移操作。复杂迁移可使用IDBVersionChangeEvent
的oldVersion/newVersion属性。
const request = indexedDB.open('MyDatabase', 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (event.oldVersion < 1) {
// 初始版本创建
db.createObjectStore('books', { keyPath: 'isbn' });
}
if (event.oldVersion < 2) {
// 版本2添加新存储
db.createObjectStore('users', { keyPath: 'id' });
}
};
实现高级查询和分页
复杂查询可使用IDBKeyRange
定义范围条件,结合游标实现分页。通过bound()
、only()
等方法创建范围,游标的advance()
支持跳过指定数量记录。
const transaction = db.transaction('books', 'readonly');
const store = transaction.objectStore('books');
const range = IDBKeyRange.bound('1000', '2000');
const request = store.openCursor(range);
let page = 1;
const results = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && page === 1) {
results.push(cursor.value);
if (results.length >= 10) {
page++;
cursor.continue();
}
}
};
处理错误和调试技巧
监听事务的onerror
事件捕获操作错误。使用浏览器开发者工具的Application面板查看IndexedDB内容。注意事务自动提交机制,避免长时间运行的事务。
transaction.onerror = (event) => {
console.error('Transaction error:', event.target.error);
};
// 调试时检查数据库状态
console.log(db.objectStoreNames);
优化性能和内存使用
大量数据操作时应分批次处理,避免内存溢出。使用游标而非getAll()
处理大数据集。合理设计索引避免过度占用存储空间。定期清理过期数据维持性能。
function processLargeDataset(store, callback) {
const request = store.openCursor();
const batchSize = 100;
let batch = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
batch.push(cursor.value);
if (batch.length >= batchSize) {
callback(batch);
batch = [];
}
cursor.continue();
} else if (batch.length > 0) {
callback(batch);
}
};
}
集成Service Worker实现完全离线
结合Service Worker缓存静态资源,用IndexedDB存储动态数据。在fetch事件中优先返回缓存,失败时从IndexedDB获取数据。通过postMessage实现页面与Worker间通信。
// Service Worker中
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetchFromIndexedDB(event))
);
});
async function fetchFromIndexedDB(event) {
// 根据请求URL从IndexedDB获取数据
// 返回符合Response接口的对象
}
测试和浏览器兼容性验证
使用不同浏览器验证功能,注意Safari的私有前缀实现。测试存储限额和异常处理。现代浏览器基本支持IndexedDB 2.0规范,但旧版本可能需要polyfill。
// 兼容性处理
const indexedDB = window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB;
通过以上步骤,可以构建功能完善的离线Web应用。实际开发中建议封装成模块或使用库如Dexie.js简化操作。定期备份重要数据到服务器,确保数据安全。