我们比较常见的前端存储方案有:cookie
、sessionStorage
、localStorage
这三种
他们都有一个共同的特点:就是可存储空间小
cookie
:大修改只有4kb
sessionStorage
:大约有5M
,只能在当前页面共享,而且关闭页面就会清除数据localStorage
:同样有5M
,永久性存储
但是,当我们想要在本地缓存大数据的时候,以上三个方法显然都不能满足,这时候就可以使用indexedDB
1、初识indexedDB
1.1 什么是indexedDB
indexedDB
是html5
提供的一种存储结构化数据的方案,可以简单理解为是类似于MySQL
的前端数据库,但是它是属于非关系型的数据库
之所以能称之为数据库,那是因为:
- 存储空间大,存储空间为磁盘空间的
50%
- 提供一套
API
,方便对象的存储和获取,同时支持查询和搜索 - 永久性存储
注意:
- 当存储空间操作限制时,会发起一个源回收的的过程,将整个数据库删除,不存在只删除一部分
- 数据其实就存储在当前浏览器所在的文件夹,一般是在C盘:
-
C:\Users\ASUS\AppData\Local\Google\Chrome\User Data\Default\IndexedDB
1.2 indexedDB
的特点
- 非关系型数据库
- 传统的数据库如
MySQL
,他们是以一张二维的表来存储数据的 indexedDB
属于非关系统数据库,它是以键值对来存储数据的
- 异步操作
indexedDB
大多数API都是异步的,操作数据库的同时不会阻塞其他操作- 而
localStorage
是属于同步操作
- 支持事务
indexedDB
支持事务,操作数据库的过程中,有一步发生错误,那么整个事物都会取消,回滚到事务发生之前,不存在只修改了某一部分
- 同源策略
- 与
localStorage
类似,indexedDB
也支持同源策略
- 存储空间大、持久化存储
2、操作indexedDB
2.1 有这么几个概念
- 仓库
- 事务
- 索引
- 游标
1)仓库objectStore
indexedDB
没有表的概念,但是我们在操作indexedDB
,都得通过createObjectStore
来创建一个仓库,我们可以把这个仓库看作是表
2)事物transaction
- 简单说,就是操作失败,会回滚到你操作之前的状态
- 进行每一步操作之前都要使用
transaction
来创建一个事物,基于这个事务进行操作
3)索引index
indexedDB
数据库的每条数据都可以有索引,当我们在创建仓库的时候可以同时创建索引- 后续操作可以通过索引来筛选,加快查找速率
4)游标cursor
indexedDB
中的游标就好比指针,当查询符合某一个条件的数据时,就可以使用图标一条一条往下查找- 类似
for
循环,想要获取所有数据即可通过游标来实现
indexedDB
中,想获取数据就只能通过主键(自定义的键)、索引、游标来查询
2.2 indexedDB
的使用
使用步骤:
1. 创建数据库
通过indexedDB.open()
方法,接收两个参数
- 数据库名称
- 指定版本号
这时就会发送一个请求,若数据存在则打开数据库;否则创建并打开数据库,同时会返回一个实例,可以在实例上添加onerror
和 onsuccess
事件,监听数据库的打开状态
直接看代码:
let db = null;
const dbName = 'DBTest';
const version = 1;
// 创建一个数据库
const request = indexedDB.open(dnName, vertion)
// 添加处理程序
request.onsuccess = (event) => {
db = event.target.result
}
request.onerror = (event) => {
console.log('呜呼---> ', event)
}
创建数据库之后,就会触发onupgradeneeded
事件,在这个事件中我们可以创建仓库
request.onupgradeneeded = (event) => {
db = event.target.result
// 如果该仓库已经存在,删除
if (db.objectStoreNames.contains('users')) {
db.deleteObjectStore('users')
}
// 创建users仓库
db.createObjectStore('users', { keyPath: 'id' });
}
createObjectStore()
接收两个参数
- 第一个参数:仓库名称
- 第二个参数:
keyPath
是指定主键
啥意思呢?就是仓库好比一个数组,里面存储着一个个的对象:[{id: ...}, {id: ...}, {id: ...}, ...]
,这个keyPath
就是指定每个对象的属性名,即键名
经过以上步骤,你大概就可以在浏览器中看到以上的结果,说明数据库创建成功,接下来就是可以进行CRUD
了
2. 创建事务
创建对象存储后,就需要创建事务了,后续的操作都是通过事物来完成的
- 通过调用
transaction()
来创建事物
const transaction = db.transaction()
它接收两个参数:
- 指定要访问的
store
仓库的名称 —— 字符串 或 字符串数组 - 访问的方式:
readonly
(只读,默认)、readwrite
读写、versionchange
版本改变
// 对users仓库进行读写
const transaction = db.transaction('users', 'readwrite)
// 对多个store操作的话 使用数组
const transaction = db.transaction(['A', 'B'], 'readwrite)
注意:transaction
参数为空,默认对所有store
都有只读权限(建议每次都指定store
)
3. 添加数据
有了事务那么就可以进行操作了
- 添加数据使用
add()
方法
const addData = (db, storeName, data) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
// objectStore() 方法获取对应的store
const store = transaction.objectStore(storeName)
const request = store.add(data) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('添加成功---> ', event.target.result);
}
request.onerror = (event) => {
console.log('添加失败---> ', event);
}
}
4. 获取数据
通过get()
方法获取数据
// 通过主键key获取数据
const getData = (db, storeName, key) => {
const transaction = db.transaction(storeName, 'readonly') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.get(key) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('获取成功---> ', event.target.result);
}
request.onerror = (event) => {
console.log('获取失败---> ', event);
}
}
5. 更新数据
更新数据使用put()
方法
// 更新data数据
const putData = (db, storeName, data) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.put(data) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('更新成功---> ', event.target.result);
}
request.onerror = (event) => {
console.log('更新失败---> ', event);
}
}
put()
和 add()
很相似,都是接收data
数据,然后存储到store
中;不同的是如果store
存在同名的主键,add()
会报错,而put()
方法会重写该数据
6.删除数据
delete()
方法用于删除数据
// 删除主键为key的数据
const deleteData = (db, storeName, key) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.delete(key) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('删除成功---> ', event);
}
request.onerror = (event) => {
console.log('删除失败---> ', event);
}
}
7. 游标查询
indexedDB
中的游标就有一个for
循环的功能,可以获取、过滤store
中的数据
- 通过
openCursor()
方法来创建一个游标
const cursor = (db, storeName, key) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.openCursor() // 创建新的请求对象
request.onsuccess = (event) => {
const cursor = event.target.result // 获取第一个游标(指针)
// 切记,永远要检查
if (cursor) {
// 执行一些操作
console.log('当前的数据--> ', cursor);
cursor.continue() // 移动到下一个
}
}
request.onerror = (event) => {
console.log(event);
}
}
2.3 使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>indexedDB</title>
</head>
<body>
<script>
var db = null;
var storeName = 'users';
var databaseName = 'DBTest';
var version = 1;
const request = indexedDB.open(databaseName, version)
request.onerror = (err) => {
console.log(err);
}
// indexDB打开成功
request.onsuccess = (event) => {
db = event.target.result
console.log('db--> ', db);
}
request.onupgradeneeded = (event) => {
db = event.target.result
// 如果该仓库已经存在,删除
if (db.objectStoreNames.contains(storeName)) {
db.deleteObjectStore(storeName)
}
// 创建该仓库
db.createObjectStore(storeName, { keyPath: 'id' });
}
const addData = (db, storeName, data) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
// objectStore() 方法获取对应的store
const store = transaction.objectStore(storeName)
const request = store.add(data) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('添加成功---> ', event.target.result);
}
request.onerror = (event) => {
console.log('添加失败---> ', event);
}
}
// 通过主键key获取数据
const getData = (db, storeName, key) => {
const transaction = db.transaction(storeName, 'readonly') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.get(key) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('获取成功---> ', event.target.result);
}
request.onerror = (event) => {
console.log('获取失败---> ', event);
}
}
// 更新data数据
const putData = (db, storeName, data) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.put(data) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('更新成功---> ', event.target.result);
}
request.onerror = (event) => {
console.log('更新失败---> ', event);
}
}
// 删除主键为key的数据
const deleteData = (db, storeName, key) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.delete(key) // 创建新的请求对象
request.onsuccess = (event) => {
console.log('删除成功---> ', event);
}
request.onerror = (event) => {
console.log('删除失败---> ', event);
}
}
// 删除主键为key的数据
const cursor = (db, storeName, key) => {
const transaction = db.transaction(storeName, 'readwrite') // 创建事务
const store = transaction.objectStore(storeName) // 获取仓库
const request = store.openCursor() // 创建新的请求对象
request.onsuccess = (event) => {
const cursor = event.target.result // 获取第一个游标(指针)
// 切记,永远要检查
if (cursor) {
// 执行一些操作
console.log('当前的数据--> ', cursor);
cursor.continue() // 移动到下一个
}
}
request.onerror = (event) => {
console.log(event);
}
}
const data1 = {
id: 1,
name:'张一',
age: 1,
email:'zhangsan@example.com'
}
const data2 = {
id: 2,
name:'张二',
age: 2,
email:'zhangsan@example.com'
}
const data3 = {
id: 1,
name:'张三',
age: 3,
email:'zhangsan@example.com'
}
// 注意:我们设置主键为id,那么添加的数据中必须要有id这个键
setTimeout(() => {
addData(db, storeName, data1) // 添加
getData(db, storeName, 1) // 获取
putData(db, storeName, data2) // 更新(此时数据库不存在该数据,会直接添加)
putData(db, storeName, data3) // 更新以存在的数据
deleteData(db, storeName, 1) // 删除,通过主键
}, 500)
</script>
</body>
</html>
- 由于创建indexedDB的操作为异步的,故需要将CRUD的操作变成异步的
- 否则在创建事务的时候,db还未被赋值会导致报错
- 更好的做法是使用
Promise
执行上面的操做,就可以看到:
3、总结
indexedDB
是一个非关系型的前端数据库,所以操作都是基于事务来进行的,但某一步出错,会回滚到操作事务之前,并且操作都是异步的,不会影响到浏览器的其他操作
操作步骤:
打开数据库 -> 创建一个仓库 -> 创建事务、基于事务获取仓库 -> add/get/delete/put