IndexedDB 是一个运行在浏览器中的事务型数据库系统,用于在客户端存储大量结构化数据
核心特点:
1.键值对存储:基于 JavaScript 对象存储数据
2.异步操作:所有操作都是异步的,不会阻塞主线程
3.支持事务:保证数据操作的原子性和一致性
4.大容量存储:通常可以存储数百MB甚至GB级别的数据
5.索引查询:支持创建索引进行高效查询
6.同源策略:每个源都有独立的数据库空间
使用方法:
- 打开数据库:indexedDB.open()
const request = indexedDB.open(name, version);
name (字符串): 数据库的名称。如果指定的数据库不存在,则会创建该数据库。
version (可选, 整数): 数据库的版本。当打开一个版本号比当前已存在的数据库版本号高的数据库时,会触发 onupgradeneeded 事件。如果未指定,则默认打开当前版本的数据库(如果数据库已存在)或版本1的数据库(如果数据库不存在)。
- 删除数据库:indexedDB.deleteDatabase()
const request = indexedDB.deleteDatabase(name);
name (字符串): 要删除的数据库的名称。
- 创建对象存储:IDBDatabase.createObjectStore()
// 该方法在 onupgradeneeded 事件中调用。
const objectStore = db.createObjectStore(name, options);
name (字符串): 对象存储的名称。
options (可选, 对象): 配置对象,包含以下属性:
keyPath (可选, 字符串或数组): 指定对象存储中用作键的属性路径。如果指定,则存储的每个对象必须在该路径上具有唯一值。如果省略,则必须使用显式键(如 add 方法提供键)。
autoIncrement (可选, 布尔值): 如果为 true,则对象存储使用自动递增的键生成器。默认为 false。
- 创建索引:IDBObjectStore.createIndex()
const index = objectStore.createIndex(name, keyPath, options);
name (字符串): 索引的名称。
keyPath (字符串或数组): 指定索引所基于的对象属性的路径。如果是数组,则使用复合键。
options (可选, 对象): 配置对象,包含以下属性:
unique (可选, 布尔值): 如果为 true,则索引不允许重复值。默认为 false。
multiEntry (可选, 布尔值): 如果为 true,则当 keyPath 解析为数组时,索引将为数组中的每个元素创建一个条目。如果为 false,则它将整个数组作为单个条目。默认为 false。
- 事务:IDBDatabase.transaction()
const transaction = db.transaction(storeNames, mode);
storeNames (字符串或字符串数组): 事务要涉及的对象存储的名称。可以是一个字符串(单个存储)或字符串数组(多个存储)。
mode (可选, 字符串): 事务模式,可以是以下之一:
"readonly": 只读,默认值。
"readwrite": 读写。
"versionchange": 用于版本变更(如创建/删除对象存储和索引)。通常只在 onupgradeneeded 事件中使用。
- 添加数据:IDBObjectStore.add()
const request = objectStore.add(value, key);
value: 要存储的值。
key (可选): 用于标识记录的键。如果对象存储使用 keyPath,则不应提供此参数,否则必须提供。
- 更新数据:IDBObjectStore.put()
const request = objectStore.put(value, key);
value: 要存储的值。
key (可选): 用于标识记录的键。如果对象存储使用 keyPath,则可以从值中提取键,否则必须提供。
- 删除数据:IDBObjectStore.delete()
const request = objectStore.delete(key);
key: 要删除的记录的键。
- 清空对象存储:IDBObjectStore.clear()
const request = objectStore.clear();
无参数。
- 获取数据:IDBObjectStore.get()
const request = objectStore.get(key);
key: 要获取的记录的键。
- 获取所有数据:IDBObjectStore.getAll()
const request = objectStore.getAll(query, count);
query (可选): 键范围或键,用于限制返回的记录。如果未提供,则返回所有记录。
count (可选): 指定要返回的记录数量。如果未提供,则返回所有匹配的记录。
- 获取键:IDBObjectStore.getKey()
const request = objectStore.getKey(key);
key: 要获取的记录的键,返回该键对应的主键(如果存在)。
- 获取所有键:IDBObjectStore.getAllKeys()
const request = objectStore.getAllKeys(query, count);
query (可选): 键范围或键,用于限制返回的键。如果未提供,则返回所有键。
count (可选): 指定要返回的键的数量。如果未提供,则返回所有匹配的键。
- 计数:IDBObjectStore.count()
const request = objectStore.count(key);
key (可选): 键范围或键,用于限制计数的记录。如果未提供,则计数所有记录。
- 打开游标:IDBObjectStore.openCursor()
const request = objectStore.openCursor(range, direction);
range (可选): 键范围,用于限制游标遍历的记录。如果未提供,则遍历所有记录。
direction (可选, 字符串): 游标遍历的方向,可以是以下之一:
"next": 默认值,从第一个记录开始向前遍历。
"nextunique": 向前遍历,但跳过重复的索引键(仅用于索引游标)。
"prev": 从最后一个记录开始向后遍历。
"prevunique": 向后遍历,但跳过重复的索引键(仅用于索引游标)。
- 打开键游标:IDBObjectStore.openKeyCursor()
const request = objectStore.openKeyCursor(range, direction);
参数同 openCursor,但游标只返回键而不返回值。
- 索引方法
索引对象(IDBIndex)具有与对象存储类似的方法,如 get, getKey, getAll, getAllKeys, count,
openCursor, openKeyCursor。它们的参数与对象存储的对应方法相同,但操作的是索引而不是整个对象存储
。
18. 游标方法
游标(IDBCursor)有以下方法:
continue(key): 将游标移动到下一个位置(或指定的键)。
key (可选): 如果提供,游标将移动到指定的键。
continuePrimaryKey(key, primaryKey): 仅用于索引游标,移动到指定的索引键和主键。
key: 索引键。
primaryKey: 主键。
advance(count): 将游标向前移动指定数量的记录。
count: 要跳过的记录数。
update(value): 更新当前游标位置的记录。
value: 新的值。
delete(): 删除当前游标位置的记录。
- 键范围
键范围(IDBKeyRange)的静态方法:
IDBKeyRange.only(value): 创建一个只包含指定键的范围。 value: 键值。
IDBKeyRange.lowerBound(lower, open): 创建一个从指定键开始到无穷大的范围。
lower: 下界键值。
open (可选, 布尔值): 如果为 true,则范围不包括下界。默认为 false。
IDBKeyRange.upperBound(upper, open): 创建一个从无穷小到指定键的范围。
upper: 上界键值。
open (可选, 布尔值): 如果为 true,则范围不包括上界。默认为 false。
IDBKeyRange.bound(lower,upper, lowerOpen, upperOpen): 创建一个介于两个键之间的范围。
lower: 下界键值。
upper:上界键值。
lowerOpen (可选, 布尔值): 如果为 true,则范围不包括下界。默认为 false。
upperOpen (可选,布尔值): 如果为 true,则范围不包括上界。默认为 false。
封装方法与案例:
class IndexedDBManager {
constructor(dbName, version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
// 初始化数据库
async init() {
this.db = await this.openDatabase();
return this;
}
// 打开数据库
openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
this.setupDatabase(db, event.oldVersion);
};
});
}
// 设置数据库结构
setupDatabase(db, oldVersion) {
// 创建用户表
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', {
keyPath: 'id',
autoIncrement: true
});
store.createIndex('email', 'email', { unique: true });
store.createIndex('name', 'name', { unique: false });
store.createIndex('age', 'age', { unique: false });
store.createIndex('createdAt', 'createdAt', { unique: false });
}
// 版本2:添加产品表
if (oldVersion < 2 && !db.objectStoreNames.contains('products')) {
const store = db.createObjectStore('products', {
keyPath: 'sku'
});
store.createIndex('category', 'category', { unique: false });
store.createIndex('price', 'price', { unique: false });
}
}
// 通用CRUD操作
async add(storeName, data) {
const store = this.getStore(storeName, 'readwrite');
return await this.promisifyRequest(store.add(data));
}
async get(storeName, key) {
const store = this.getStore(storeName, 'readonly');
return await this.promisifyRequest(store.get(key));
}
async put(storeName, data) {
const store = this.getStore(storeName, 'readwrite');
return await this.promisifyRequest(store.put(data));
}
async delete(storeName, key) {
const store = this.getStore(storeName, 'readwrite');
return await this.promisifyRequest(store.delete(key));
}
async getAll(storeName) {
const store = this.getStore(storeName, 'readonly');
return await this.promisifyRequest(store.getAll());
}
async getByIndex(storeName, indexName, value) {
const store = this.getStore(storeName, 'readonly');
const index = store.index(indexName);
return await this.promisifyRequest(index.get(value));
}
// 获取存储空间
getStore(storeName, mode) {
const transaction = this.db.transaction([storeName], mode);
return transaction.objectStore(storeName);
}
// 将请求转换为Promise
promisifyRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 关闭数据库
close() {
if (this.db) {
this.db.close();
this.db = null;
}
}
}
// 使用示例
async function example() {
const dbManager = new IndexedDBManager('MyAppDB', 2);
await dbManager.init();
// 添加用户
const userId = await dbManager.add('users', {
name: '李白',
email: 'libai@example.com',
age: 29,
createdAt: new Date()
});
// 获取用户
const user = await dbManager.get('users', userId);
// 通过邮箱查找用户
const userByEmail = await dbManager.getByIndex('users', 'email', 'zhang@example.com');
// 更新用户
await dbManager.put('users', {
id: userId,
...user,
age: 26,
updatedAt: new Date()
});
// 删除用户
await dbManager.delete('users', userId);
dbManager.close();
}
example().catch(console.error);
4122

被折叠的 条评论
为什么被折叠?



