很多场景其实不用跟后端进行一个交互也能实现长效的持久化,想必各位在开发中也会用到浏览器缓存这个概念,那么你知道在缓存里其实还存在着indexedDB这个功能莫?
什么是IndexedDB?
IndexedDB是一种低级API,用于客户端存储大量结构化数据,并提供了索引、事务、查询和游标等数据库特性。与localStorage和sessionStorage不同,IndexedDB是一个真正的数据库,它允许我们在用户的浏览器中存储几乎无限制的数据量,并通过复杂的查询来访问这些数据。
方法介绍:
-
open()
-
功能:打开或创建一个数据库。
-
参数:
name:数据库的名称(字符串)。version:数据库的版本号(整数)。如果省略,打开已有数据库时默认为当前版本;新建数据库时默认为1。
-
返回值:返回一个
IDBOpenDBRequest对象,用于处理打开数据库的结果。 -
事件:
onsuccess、onerror、onupgradeneeded。
-
-
createObjectStore()
-
功能:在数据库的版本升级事件中创建新的对象仓库(Object Store)。
-
参数:
name:对象仓库的名称(字符串)。options:一个对象,包含对象仓库的配置选项,如keyPath(主键路径)和autoIncrement(是否自动生成主键)。
-
返回值:无。
-
-
createIndex()
-
功能:在对象仓库中创建索引。
-
参数:
indexName:索引的名称(字符串)。keyPath:索引基于的键路径(字符串或数组)。options:一个对象,包含索引的配置选项,如unique(是否唯一)。
-
返回值:无。
-
-
transaction()
-
功能:开始一个事务,用于执行对数据库的读写操作。
-
参数:
storeNames:一个包含对象仓库名称的数组或单个对象仓库名称,指定事务将访问哪些对象仓库。mode:事务的模式(字符串),如readonly(只读)、readwrite(读写)。
-
返回值:返回一个
IDBTransaction对象,用于管理事务。
-
-
objectStore()
- 功能:通过事务对象获取指定的对象仓库。
- 参数:对象仓库的名称(字符串)。
- 返回值:返回一个
IDBObjectStore对象,用于对对象仓库进行操作。
-
add() 、put() 、delete() 、get() 、clear()
- 这些方法都是在
IDBObjectStore对象上调用的,用于向对象仓库中添加、更新、删除、获取和清空数据。
- 这些方法都是在
为什么用IndexedDB?
其实简单的存储一些小东西localStorage类完全够用,因为indexedDB它具有数据库的一些特性,例如原子性,
那么什么情况下要用到它呢?
1、存储量大,超过5M:例如存储一些大图预览、用canvas画一次之后存到indexedDB上就可直接拿。
2、庞大的数据列表:我们可以创建索引,通过索引,我们可以快速地检索到满足特定条件的数据,而无需遍历整个数据集。
3、事务支持:这是数据库的一个特性,事务可以确保数据的一致性和完整性,即使在发生错误或异常时也能保持数据的正确性。通过事务,我们可以执行一系列的数据库操作,要么全部成功,要么在遇到错误时全部回滚。
4、异步操作:它的所有操作都是异步的,这意味着它们不会阻塞UI线程,从而提高了Web应用的响应性和用户体验。我们可以使用回调函数、Promises或async/await等机制来处理异步操作的结果。
原子性:是事务的一个特性,要么全部成功,要么在遇到错误时全部回滚。
用法例子
1、基本用法
打开/新建数据库
let request = window.indexedDB.open('MyDatabase', 1);
request.onerror = (event) => {
console.error("error: " + event.target.errorCode);
};
request.onsuccess = (event) => {
let db = event.target.result;
console.log("数据库打开成功!");
};
当看到这里的时候就证明创建连接成功了!
创建表(创建对象存储空间)
在这里我们接着用上面的数据库的onupgradeneeded方法创建表,该方法接收两个参数:对象存储空间的名称和一个配置对象(可选),配置对象中可以指定主键(keyPath)和是否自动增长(autoIncrement)等。
request.onupgradeneeded = (event) => {
let db = event.target.result;
let objectStore = db.createObjectStore('MyObjectStore', { keyPath: 'id', autoIncrement: true });
// 创建索引
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });
console.log(objectStore)
};
执行过后你就会看到一个
MyObjectStore的表并且存在name、email这两个索引。
增加和删除数据
对数据库的操作(增删查改等)都需要通过事务(Transaction)来完成。事务具有三种模式:readonly(只读)、readwrite(读写)和versionchange(版本变更)。增加数据通常使用add()方法,删除数据使用delete()方法。
新增:
request.onsuccess = (event) => {
let db = event.target.result;
let transaction = db.transaction(['MyObjectStore'], 'readwrite');
let objectStore = transaction.objectStore('MyObjectStore');
let request = objectStore.get('san@example.com');
request.onsuccess = (event) => {
if (request.result) {
// 数据已存在,可以选择更新或不做任何操作
console.log('邮件已存在:', 'san@example.com');
} else {
let data = { name: '张三', age: 25, email: 'san@example.com' };
objectStore.add(data);
}
};
request.onerror = (event) => {
console.error('添加失败', event);
};
};
删除:
request.onsuccess = (event) => {
let db = event.target.result;
let transaction = db.transaction(['MyObjectStore'], 'readwrite');
let objectStore = transaction.objectStore('MyObjectStore');
let deleteRequest = objectStore.delete(1);
deleteRequest.onsuccess = (event) => {
console.log('删除成功');
};
deleteRequest.onerror = (event) => {
console.error('删除失败');
};
};
刷新下数据库你就会发现已被删除
读取和更新数据:
读取数据通常使用get()方法,根据主键检索数据。更新数据则可以使用put()方法,如果记录存在则更新,不存在则新增。
读取:
request.onsuccess = (event) => {
let db = event.target.result;
let transaction = db.transaction(['MyObjectStore'], 'readwrite');
let objectStore = transaction.objectStore('MyObjectStore');
let table = objectStore.get(2);
table.onsuccess = (e) => {
console.log(table)
if (table.result) {
// 数据已存在,可以选择更新或不做任何操作
console.log('邮件已存在:', table.result);
} else {
let data = { name: '张三', age: 25, email: 'san@example.com' };
objectStore.add(data);
}
};
};
更新:
request.onsuccess = (event) => {
let db = event.target.result;
let transaction = db.transaction(['MyObjectStore'], 'readwrite');
let objectStore = transaction.objectStore('MyObjectStore');
let newData = {
id: 3,
name: '李四',
age: 30,
email: 'li@example.com',
};
let putRequest = objectStore.put(newData);
putRequest.onsuccess = (event) => {
console.log('数据更新成功');
};
putRequest.onerror = (event) => {
console.error('数据更新失败!');
};
};
看到这个就证明插入一条数据成功啦!
异步操作 拿下面这段查询语句来说
request.onsuccess = (event) => {
let db = event.target.result;
let transaction = db.transaction(['MyObjectStore'], 'readwrite');
let objectStore = transaction.objectStore('MyObjectStore');
let table = objectStore.get(2);
table.onsuccess = (e) => {
if (table.result) {
// 数据已存在,可以选择更新或不做任何操作
console.log('邮件已存在:', table.result);
} else {
let data = { name: '张三', age: 25, email: 'san@example.com' };
objectStore.add(data);
}
};
console.log('我在后,但是我先');
};
证明操作是异步的,不会阻塞我们主线程
实际应用场景
1、例如一个庞大的表单,上百个字段(存在着图片的上传)需要用户去填写,那么当用户误操作的时候就会丢失已填写数据,那么我们就可以设计一个indexedDB专门去存储,并将用户上传的图片转为base64存储。
代码如下:
export function fileToBase64(file) {
const reader = new FileReader()
reader.onload = function (e) {
storeImage(e.target.result)
}
reader.readAsDataURL(file)
}
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('FormDatabase', 1)
request.onerror = (event) => {
console.error('Database error: ' + event.target.errorCode)
reject(event.target.errorCode)
}
request.onupgradeneeded = (event) => {
const db = event.target.result
if (!db.objectStoreNames.contains('FormData')) {
db.createObjectStore('FormData', { keyPath: 'id', autoIncrement: true })
}
if (!db.objectStoreNames.contains('Images')) {
db.createObjectStore('Images', { keyPath: 'id', autoIncrement: true })
}
}
request.onsuccess = (event) => {
resolve(event.target.result)
}
})
}
const handleDataBase = (
name,
event
) => {
return async (
id, // 不同表单对应的不同唯一ID
value
) => {
const db = await openDB()
const tx = db.transaction(name, event)
const store = tx.objectStore(name)
store.put({ id, value: JSON.stringify(value) }) // 存储表单大字段
return tx.complete
}
}
export const storeFormData = handleDataBase(
'FormData',
'readwrite'
)
export const storeImage = handleDataBase(
'Images',
'readwrite'
)
export const getImageAndDataById = async (id) => {
const db = await openDB()
const requestToPromise = (request) => {
return new Promise((resolve, reject) => {
request.onsuccess = event => resolve(event.target.result);
request.onerror = event => reject(event.target.error);
});
}
const images = db.transaction('Images', 'readonly')
const tIamge = images.objectStore('Images')
const formData = db.transaction('FormData', 'readonly')
const tFormData = formData.objectStore('FormData')
return new Promise(async (resolve, reject) => {
try {
const [imgs, data] = await Promise.all([
requestToPromise(tIamge.get(id)),
requestToPromise(tFormData.get(id))
]);
resolve({
imgs,
data
})
} catch (e) {
reject(e)
}
})
}
我们测验下:
storeFormData(1, { name: '张三', age: 18 })
getImageAndDataById(1).then((data) => {
console.log(data)
})
2、当我们编写一个款设计器的时候,用户的节点信息,生成的图片预览、皆可存储至
indexedDB。
代码操作类似上面例子。
IndexedDB因其大量数据存储能力、结构化数据存储、高效的查询能力、事务支持、异步操作、持久化存储以及强大的灵活性和可扩展性等优点,成为我们存储大量结构化数据的首选解决方案。