解锁Web数据存储:浏览器数据库 IndexedDB
1、浏览器数据存储
浏览器中的数据存储,日常接触较多的有Cookie、SessionStorage、LocalStorage,接下来引入一个重头嘉宾:IndexedDB;下面看它们四个在生命周期、存储大小、存储形式上的对比:
Cookie
- [ 生命周期 ] 由服务器生成,可以设置过期时间;过期后由浏览器自动清除;
- [ 存储空间 ] 4KB
- [ 数据形式 ] 以字符串键值对的形式存储数据;
- [ 配置项 ]
Expires/Max-Age | 过期时间 |
Path | 有效路径,/表示对当前网站所有路径有效 |
Domain | 有效域名) |
HttpOnly | 配置后只允许http访问,不允许js访问 |
Secure | 只会在https中发送 |
SessionStorage
- [ 生命周期 ] 客户端主动存储;仅在当前页面存在时生效,页面关闭后不会保留数据;
- [ 存储空间 ] 5MB
- [ 数据形式 ] 以字符串键值对的形式存储数据
LocalStorage
- [ 生命周期 ] 客户端主动存储;除非手动清除,否则一直存在;
- [ 存储空间 ] 5MB
- [ 数据形式 ] 以字符串键值对的形式存储数据
2、IndexedDB
IndexedDB 是一种在浏览器中存储大量结构化数据的数据库
特点
- 存储容量大:
- 相比 LocalStorage 和 SessionStorage ,IndexedDB 提供了更大的存储容量,通常在几百 MB 到 GB 级,具体取决于浏览器和设备。这使得它可以存储大量数据,例如可以存储整个应用程序的数据,包括离线数据、多媒体文件等。
- 异步操作:
- IndexedDB 的操作是异步的,不会阻塞主线程,这对于性能至关重要,尤其是在存储和检索大量数据时。它使用事件和回调机制,确保在执行长时间操作时不会影响用户界面的响应性。例如,使用 IndexedDB 存储数据时,不会导致页面冻结,因为操作是在后台进行的。
使用步骤
MDN对于IndexedDB的使用指南:IndexedDB - Web API | MDN
1、打开数据库,调用indexedDB.open()
// 打开我们的数据库
// @param {string} --数据库名称
// @param {number} --数据库版本号
const request = window.indexedDB.open("MyTestDatabase", 1);
let db;
// 打开MyTestDatabase数据库失败时触发
request.onerror = function(event) {
console.error('Error opening IndexedDB:', event.target.errorCode);
};
// 打开MyTestDatabase数据库成功时触发
request.onsuccess = function(event) {
console.log('IndexedDB 打开成功');
db = event.target.result;
};
- 如果数据库不存在,open 操作会创建该数据库,然后触发 onupgradeneeded 事件,你需要在该事件的处理器中创建数据库模式。
- 如果数据库已经存在,但你指定了一个更高的数据库版本,会直接触发 onupgradeneeded 事件,允许你在处理器中更新数据库模式。
- 不能使用浮点数,否则它将会被转换成不超过它的最近整数,这可能导致事务无法启动,upgradeneeded 事件也不会被触发。
2、在数据库中创建对象存储(object store),使用createObjectStore来创建对象存储
// createObjectStore函数
// @param {string} --对象存储的名称
// @param {object} --其中keyPath定义的是主键
request.onupgradeneeded = function(event) {
db = event.target.result;
if (!db.objectStoreNames.contains('UserStore')) {
db.createObjectStore('UserStore', { keyPath: 'userId' });
}
};
onupgradeneeded 事件的触发时机
- 初次创建数据库:当使用
indexedDB.open()
方法打开一个不存在的数据库时,会触发onupgradeneeded
事件,以便在数据库创建时进行初始化操作,如创建对象存储、定义索引等。 - 版本号更新:如果已经存在的数据库的版本号与
indexedDB.open()
方法中指定的版本号不一致,也会触发onupgradeneeded
事件。这通常用于对数据库结构进行升级,例如添加新的对象存储、修改索引或删除不必要的对象存储等。
3、启动事务,来执行一些数据库操作,如添加或获取数据等
「 3-1 新增数据 」
// transaction函数
// @param {string[]} --想要访问的对象存储的数组
// @param {readonly | readwrite } --事务模式
// 调用 add函数
function cacheUserData(userId, saveData) {
// 创建一个涉及userdata对象存储的读写事务
const transaction = db.transaction(['UserStore'], 'readwrite');
// 获取userdata对象存储,为后续对该对象存储的数据操作做铺垫
const store = transaction.objectStore('UserStore');
// 对userdata对象存储进行数据添加
const request = store.add({ userId, saveData });
// 执行成功
request.onsuccess = function(event) {
console.log('UserStore cached successfully:', userId);
};
// 执行失败
request.onerror = function(event) {
console.error('Error caching Data:', event.target.error);
};
}
// 添加数据
const mydata = {
name: '昔冰',
age: 18,
gender: '男',
address: '河北省衡水市'
}
cacheUserData(Date.now(), mydata)
「 3-2 删除数据 」
// 调用 delete函数
function deleteUserData(userId){
if (!userId) return alert('参数错误')
const transaction =db.transaction(['UserStore'],'readwrite')
const store=transaction.objectStore('UserStore')
const request=store.delete(userId)
request.onsuccess = function(event) {
console.log("数据删除成功");
};
request.onerror = function(event) {
console.error("数据删除失败", event.target);
};
}
「 3-3 查询数据 」
// 按id查询数据 get()
function queryUserDataById(userId) {
if (!userId) return alert('参数错误')
const transaction = db.transaction(['UserStore'], 'readonly')
const store = transaction.objectStore('UserStore')
const request = store.get(userId)
console.log(userId)
request.onsuccess = function (event) {
if (event.target.result) console.log('获取到数据::', event.target.result)
else console.log('未找到数据')
}
request.onerror = function (event) {
console.log('获取数据失败')
}
}
// 查询所有数据 getAll()
function queryUserData() {
const transaction = db.transaction(['UserStore'], 'readonly')
const store = transaction.objectStore('UserStore')
const data = []
const request=store.getAll()
request.onsuccess = function (event) {
if (event.target.result) console.log('获取到数据::', event.target.result)
else console.log('未找到数据')
}
request.onerror = function (event) {
console.log('获取数据失败')
}
}
「 3-4 更新数据 」
// 先调用 get函数来获取对应的数据,再通过 put函数来更新数据
function updateUserData(userId, newData = {}) {
if (!userId) return alert('参数错误')
const transaction = db.transaction(['UserStore'], 'readwrite')
const store = transaction.objectStore('UserStore')
const request = store.get(userId)
request.onsuccess = function (event) {
const data = event.target.result
// 注意这里只对data更新了saveData数据,没有改变userId,如果变动了userId则无法更新成功
Object.assign(data, { saveData: newData })
const requestUpdate = store.put(data)
requestUpdate.onerror = (event) => {
console.log('数据更新错误')
}
requestUpdate.onsuccess = (event) => {
console.log('数据更新成功')
}
}
}
// 绑定事件更新数据
const btnUpdateUser = document.querySelector('.update-user')
btnUpdateUser.addEventListener('click', () => {
const newData = {
address: '天津市和平区',
age: 19,
gender: '女',
name: '王美丽'
}
updateUserData(1736924438112, newData)
})
3、注意事项
数据库对象的赋值时机
- onupgradeneeded事件只会在初次创建数据库的时候会执行,或者更新数据库版本的时候会执行,所以对于数据库对象db如果在onupgradeneeded中进行赋值,需要考虑的是onupgradeneeded可能由于数据库非首次创建或版本未更新导致不执行对应的事件函数,致使db不会被赋值,后续再通过db来调用api时会报错,比如:Cannot read properties of undefined (reading 'transaction')。
- 虽然数据库对象在onupgradeneeded的事件函数参数中可以获取到,但在数据库打开成功后的onsuccess事件函数参数中也可以获取到,而onsuccess会在每一次数据库成功打开后都会执行,所以可以将对db的赋值操作放置在onsuccess中。
数据修改操作
- 如果只使用autoIncrement : true,配置为主键自增,没有主键字段,在进行数据修改时,直接将数据对象传入会对这个新的数据进行新增而非修改。
- 配置autoIncrement : true后还需要再配置一个主键字段,这样后续添加数据时也不需要考虑主键字段的值,会在添加时自动添加;当然在修改时,可以根据主键字段来进行数据的更新。
// 创建对象存储时
db.createObjectStore('UserStore', { keyPath: 'userId', autoIncrement: true })
// 添加数据时
const addBtn = document.querySelector('.addUser')
addBtn.addEventListener('click', () => {
// 创建一个事务,找对应的对象存储做对应的操作
const transaction = db.transaction(['UserStore'], 'readwrite')
// 获取具体你要操作的对象存储
const store = transaction.objectStore('UserStore')
// 新增数据
const obj = {
// 不需要配置userId及数据
userData: {
id: Date.now(),
name: '昔冰',
age: 18,
address: '衡水市'
}
}
// add方法,会返回一个执行对象,可以监听到成功或者失败
const flag = store.add(obj)
flag.onsuccess = function (event) {
console.log('新增数据-成功!')
}
flag.onerror = function (event) {
console.log('新增数据-失败!')
}
})
// 修改数据时
const updateUserData(id)=>{
// 创建一个事务,找对应的对象存储做对应的操作
const transaction = db.transaction(['UserStore'], 'readwrite')
// 获取具体你要操作的对象存储
const store = transaction.objectStore('UserStore')
// add方法,会返回一个执行对象,可以监听到成功或者失败
const flag = store.get(1) // 主键为1,非索引!!!
flag.onsuccess = function (event) {
console.log('获取数据-成功!')
// 获取到具体的数据
const data = event.target.result
data.userData.name = '爱喝水的人'
// data中是有主键userId的,所以put时直接传入data就可以被数据库知道具体是修改的哪条数据
const flag2 = store.put(data)
flag2.onsuccess = function (event) {
console.log('修改数据-成功!')
}
flag2.onerror = function (event) {
console.error('修改数据-失败!')
}
}
flag.onerror = function (event) {
console.error('获取数据-失败!')
}
}
const updateBtn = document.querySelector('.updateUser')
updateBtn.addEventListener('click',updateUserData(1))