====== 第二部分:环境搭建与第一个数据库 ======
===== 2.1 开发环境准备 =====
===== 2.1.1 浏览器支持情况 =====
IndexedDB 在所有现代浏览器中都得到了良好支持:
^ 浏览器 ^ 最低版本 ^ 备注 ^
| Chrome | 23+ | 完全支持 |
| Firefox | 16+ | 完全支持 |
| Safari | 10+ | 完全支持 |
| Edge | 12+ | 完全支持 |
| Opera | 15+ | 完全支持 |
| iOS Safari | 10.3+ | 完全支持 |
| Android Chrome | 25+ | 完全支持 |
检测 IndexedDB 支持:
function isIndexedDBSupported() {
return 'indexedDB' in window &&
indexedDB !== null &&
indexedDB !== undefined;
}
// 更详细的检测
function getIndexedDBInfo() {
const info = {
supported: false,
vendor: 'unknown',
estimatedQuota: null
};
if (!isIndexedDBSupported()) {
return info;
}
info.supported = true;
// 检测浏览器类型
const ua = navigator.userAgent;
if (ua.includes('Chrome')) info.vendor = 'chrome';
else if (ua.includes('Firefox')) info.vendor = 'firefox';
else if (ua.includes('Safari')) info.vendor = 'safari';
else if (ua.includes('Edge')) info.vendor = 'edge';
// 检测存储配额
if (navigator.storage && navigator.storage.estimate) {
navigator.storage.estimate().then(estimate => {
info.estimatedQuota = estimate.quota;
});
}
return info;
}
===== 2.1.2 开发工具 =====
**浏览器开发者工具**
Chrome/Edge 开发者工具中查看 IndexedDB:
* 打开 DevTools(F12)
* 选择 Application(应用)标签
* 左侧菜单选择 IndexedDB
* 可以查看数据库、对象存储空间、索引和数据
Firefox 开发者工具:
* 打开 DevTools
* 选择 Storage(存储)标签
* 选择 IndexedDB 部分
Safari 开发者工具:
* 需要先启用 Develop 菜单
* Preferences → Advanced → Show Develop menu
* Develop → Show Web Inspector
**推荐的 VS Code 插件:**
* ESLint:代码检查
* Prettier:代码格式化
* JavaScript (ES6) code snippets:代码片段
===== 2.2 第一个 IndexedDB 应用 =====
===== 2.2.1 打开数据库 =====
使用 indexedDB.open() 方法打开或创建数据库:
// 基本语法
const request = indexedDB.open(databaseName, version);
// 示例
const request = indexedDB.open('MyFirstDatabase', 1);
request.onsuccess = function(event) {
const db = event.target.result;
console.log('数据库打开成功');
console.log('数据库名称:', db.name);
console.log('数据库版本:', db.version);
};
request.onerror = function(event) {
console.error('数据库打开失败:', event.target.error);
};
request.onupgradeneeded = function(event) {
const db = event.target.result;
console.log('数据库需要升级');
// 在这里创建对象存储和索引
};
request.onblocked = function(event) {
console.warn('数据库被阻塞,请关闭其他标签页');
};
===== 2.2.2 理解版本控制 =====
版本号是 IndexedDB 中非常关键的概念:
// 场景一:首次创建数据库(版本 1)
const request1 = indexedDB.open('MyApp', 1);
request1.onupgradeneeded = function(event) {
const db = event.target.result;
// 首次创建对象存储
if (!db.objectStoreNames.contains('users')) {
db.createObjectStore('users', { keyPath: 'id' });
}
};
// 场景二:升级数据库(版本 2)
const request2 = indexedDB.open('MyApp', 2);
request2.onupgradeneeded = function(event) {
const db = event.target.result;
const oldVersion = event.oldVersion;
const newVersion = event.newVersion;
console.log(`从版本 ${oldVersion} 升级到 ${newVersion}`);
// 根据旧版本执行不同的升级逻辑
if (oldVersion < 2) {
// 添加新的索引
const store = request2.transaction.objectStore('users');
store.createIndex('emailIndex', 'email', { unique: true });
}
};
===== 2.2.3 创建对象存储空间 =====
在 onupgradeneeded 回调中创建对象存储:
request.onupgradeneeded = function(event) {
const db = event.target.result;
// 方法 1:使用 keyPath(内联键)
const store1 = db.createObjectStore('users', {
keyPath: 'userId'
});
// 方法 2:使用 autoIncrement(自增键)
const store2 = db.createObjectStore('logs', {
autoIncrement: true
});
// 方法 3:同时指定 keyPath 和 autoIncrement
const store3 = db.createObjectStore('orders', {
keyPath: 'orderId',
autoIncrement: true
});
// 方法 4:复合主键
const store4 = db.createObjectStore('memberships', {
keyPath: ['userId', 'groupId']
});
};
===== 2.2.4 添加第一条数据 =====
request.onsuccess = function(event) {
const db = event.target.result;
// 开启读写事务
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
// 添加数据
const addRequest = store.add({
userId: 1,
name: '张三',
email: 'zhangsan@example.com',
age: 25,
createdAt: new Date()
});
addRequest.onsuccess = function() {
console.log('数据添加成功,主键:', addRequest.result);
};
addRequest.onerror = function() {
console.error('添加失败:', addRequest.error);
};
// 事务完成
transaction.oncomplete = function() {
console.log('事务完成,数据已持久化');
db.close(); // 关闭数据库连接
};
};
===== 2.3 完整的入门示例 =====
===== 2.3.1 用户管理示例 =====
一个完整的用户管理数据库示例:
class UserDatabase {
constructor(dbName = 'UserDB') {
this.dbName = dbName;
this.db = null;
}
// 打开数据库
open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建用户对象存储
if (!db.objectStoreNames.contains('users')) {
const userStore = db.createObjectStore('users', {
keyPath: 'id',
autoIncrement: true
});
// 创建索引
userStore.createIndex('nameIndex', 'name', { unique: false });
userStore.createIndex('emailIndex', 'email', { unique: true });
userStore.createIndex('ageIndex', 'age', { unique: false });
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// 添加用户
addUser(user) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
const request = store.add(user);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 获取用户
getUser(id) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['users'], 'readonly');
const store = transaction.objectStore('users');
const request = store.get(id);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 关闭数据库
close() {
if (this.db) {
this.db.close();
this.db = null;
}
}
}
// 使用示例
(async function() {
const userDB = new UserDatabase();
await userDB.open();
// 添加用户
const userId = await userDB.addUser({
name: '张三',
email: 'zhangsan@example.com',
age: 25
});
console.log('添加用户,ID:', userId);
// 获取用户
const user = await userDB.getUser(userId);
console.log('用户信息:', user);
userDB.close();
})();
===== 2.3.2 错误处理最佳实践 =====
function handleIndexedDBError(error) {
switch (error.name) {
case 'QuotaExceededError':
console.error('存储配额不足');
// 提示用户清理数据或使用持久存储
break;
case 'VersionError':
console.error('版本号错误');
// 检查版本号逻辑
break;
case 'NotFoundError':
console.error('对象存储不存在');
// 检查数据库结构
break;
case 'InvalidStateError':
console.error('数据库连接已关闭');
// 重新打开数据库
break;
case 'AbortError':
console.error('事务被中止');
// 检查事务逻辑
break;
default:
console.error('未知错误:', error);
}
}
===== 2.4 数据库连接管理 =====
===== 2.4.1 连接生命周期 =====
class DBConnectionManager {
constructor() {
this.connections = new Map();
}
// 获取或创建连接
async getConnection(dbName, version) {
if (this.connections.has(dbName)) {
const conn = this.connections.get(dbName);
if (conn.readyState === 'done') {
return conn;
}
}
const db = await this.openConnection(dbName, version);
this.connections.set(dbName, db);
return db;
}
openConnection(dbName, version) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, version);
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
});
}
// 关闭指定连接
closeConnection(dbName) {
if (this.connections.has(dbName)) {
this.connections.get(dbName).close();
this.connections.delete(dbName);
}
}
// 关闭所有连接
closeAll() {
this.connections.forEach((db, name) => {
db.close();
console.log(`关闭数据库连接: ${name}`);
});
this.connections.clear();
}
}
===== 2.5 本章小结 =====
本章介绍了 IndexedDB 的环境搭建和基础使用:
* 所有现代浏览器都支持 IndexedDB
* 使用 indexedDB.open() 打开数据库
* 在 onupgradeneeded 中创建对象存储和索引
* 所有操作通过事务执行
* 建议对原生 API 进行 Promise 封装
继续阅读 [[part03-objectstore|第三部分:对象存储空间详解]]。