developer.mozilla.org/zh-CN/docs/…
随着浏览器版本的不断迭代,加上日益增长的需求,前端对于浏览器大容量存储的方案需求也越来越迫切。而IndexedDB就是用来解决这一方面使用场景的,我们来看前端现在各个存储方案的容量:
- Cookie:不超过4KB
- LocalStorage:根据浏览器内核的不同,存储容量在2.5-10MB
- Web SQL:已被废弃,不推荐使用 因为直接使用sql语句进行开发
- IndexedDB:没有上限,一般不少于250M
IndexedDB是新一代的Web存储方案,更接近NoSQL数据库,是一个基于事务操作的key-value型前端数据库。支持异步API。
IndexedDB特性
- 使用key-value进行存储:它的每一条数据记录都有对应的主键,并且主键是独一无二的
- 异步API:使用异步操作,在操作大数据时不会锁死浏览器,用户依然可以做其他操作
- 支持事务:异步失败会导致整个事务会滚到事务发生前的状态
- 同源限制:不能访问跨源数据库
- 存储空间大:没有上限,一般不少于250M
IndexedDB的基本概念
版本:IndexedDB有版本的概念,所以对于库表结构的修改,必须得通过升级数据库版本的方式完成
事务:上方有提到过,这样保护了数据,当错误事件发生时数据库数据会回滚
操作IndexedDB
下面我们对indexedDB来进行操作
1. 创建/打开数据库
创建和打开数据库都是使用open方法,如果没有数据库则为创建
onupgradeneeded事件:
触发条件:
- 指定的版本号大于当前版本号触发
- 新建数据库触发
获取数据库实例的方式:
- 本地有数据库则在success事件中获取
- 触发onupgradeneeded则在该事件内获取
const APP_DATABASE_NAME = 'demo'
const APP_DATABASE_VERSION = 1
export class IndexedDB {
private db!:IDBDatabase
open() {
const idbOpenDBRequest = indexedDB.open(APP_DATABASE_NAME,APP_DATABASE_VERSION)
idbOpenDBRequest.onerror = () => {
console.log('db open error')
}
idbOpenDBRequest.onsuccess = () => {
console.log('db open success')
this.db = idbOpenDBRequest.result
}
// 1. 指定的版本号大于当前版本号触发
// 2. 新建数据库也是使用open方法,这时候也会触发该事件
idbOpenDBRequest.onupgradeneeded = (event: IDBVersionChangeEvent) => {
console.log('db upgradeneeded')
var db = event.target?.result // 获取数据库实例
this.db = db
}
}
}
2. 创建表格
对于表格的结构可以想做是对象,有key和value的存在,通过db对象的objectStoreNames可以判断是否存在表格并进行处理。
createObjectStore方法的第二个参数可以传入keyPath,那么将会将keyPath传入的值作为表格对象的key,也可以设置自动设置主键,设置请看下方代码
private createObjectStore() {
// 创建表格
if(!this.db) return
const db = this.db
let objectStore;
if(!this.db.objectStoreNames.contains('user')) {
// 不存在则创建
objectStore = db.createObjectStore('user', {keyPath: 'id'})
// 自动设置主键
// objectStore = db.createObjectStore('user', {autoIncrement:true})
// 创建索引 搜索索引会更快,建议对多数量数据添加索引
objectStore.createIndex('name', 'name', {unique: false})
}
}
// 调用时机
idbOpenDBRequest.onupgradeneeded = (event: IDBVersionChangeEvent) => {
console.log('db upgradeneeded')
var db = event.target?.result // 获取数据库实例
this.db = db
this.createObjectStore()
}
3. 新增数据
对于表格的操作需要新建事务,所以调用transaction方法,第一个参数为操作的表格数组,第二个参数为操作模式,关于操作模式请看下方:
- readwrite: 读写模式
- readonly:只读
- versionchange: 操作结构
通过objectStore方法可以获取具体表格对象,并进行操作
APP_OBJECT_STORE_NAME为上方的表格名,抽成一个常量
add() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME)
.add({id: 1, name: 'user1', age: 1, sex: 1})
request.onsuccess = (e) => {
console.log('add data success', e)
}
request.onerror = (e) => {
console.log('add data error', e)
}
}
4. 读取数据
通过getAll可以获取数据,也可以通过get,getAllKeys等方法进行获取
read() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME).getAll()
request.onerror = (e) => {
console.log('get data error',e)
}
request.onsuccess = (e) => {
if(!request.result) {
console.log('no data')
return
}
const result = request.result
console.log('data array ----->', result, e)
}
}
5. 遍历数据
也可以通过forEach的方式进行数据遍历
eachAll() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME).openCursor()
request.onsuccess = (e) => {
const cursor = e.target.result
if(!cursor) return
console.log(cursor, '<------cursor')
}
}
6. 更新数据
put() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME)
.put({id: 1, name: 'user-put1', age: 2, sex: 2})
request.onsuccess = (e) => {
console.log('put data success', e)
}
request.onerror = (e) => {
console.log('put data error', e)
}
}
7. 删除数据
通过创建库表时设置的主键进行删除
delete() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME)
.delete(1)
request.onsuccess = (e) => {
console.log('put data success', e)
}
request.onerror = (e) => {
console.log('put data error', e)
}
}
8. 通过索引快捷读取数据
刚才我们所演示的读取数据只能通过主键或者遍历获取,这样子太麻烦了,我想直接通过里面的某个字段进行搜索的话。索引的重要性就凸显出来了。创建索引可以让我们根据索引字段来搜索数据
// 参数依次为索引名称,对应字段名,配置
objectStore.createIndex('name', 'name', {unique: false})
readIndexData() {
const index = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME).index('name')
const request = index.get('user1')
request.onerror = (e) => {
console.log('get data error',e)
}
request.onsuccess = (e) => {
if(!request.result) {
console.log('no data')
return
}
const result = request.result
console.log('data array ----->', result, e)
}
}
整个文件
const APP_DATABASE_NAME = 'demo'
const APP_DATABASE_VERSION = 1
const APP_OBJECT_STORE_NAME = 'user'
export class IndexedDB {
private db!:IDBDatabase
private objectStore!: IDBObjectStore
constructor() {
this.open()
}
private open() {
const idbOpenDBRequest = indexedDB.open(APP_DATABASE_NAME,APP_DATABASE_VERSION)
idbOpenDBRequest.onerror = () => {
console.log('db open error')
}
idbOpenDBRequest.onsuccess = () => {
console.log('db open success', idbOpenDBRequest.result)
this.db = idbOpenDBRequest.result
}
// 1. 指定的版本号大于当前版本号触发
// 2. 新建数据库也是使用open方法,这时候也会触发该事件
idbOpenDBRequest.onupgradeneeded = (event: IDBVersionChangeEvent) => {
console.log('db upgradeneeded')
var db = event.target?.result // 获取数据库实例
this.db = db
this.createObjectStore()
}
}
private createObjectStore() {
// 创建表格
if(!this.db) return
const db = this.db
let objectStore;
if(!this.db.objectStoreNames.contains(APP_OBJECT_STORE_NAME)) {
// 不存在则创建
objectStore = db.createObjectStore(APP_OBJECT_STORE_NAME, {keyPath: 'id'})
// 自动设置主键
// objectStore = db.createObjectStore('user', {autoIncrement:true})
// 创建索引 搜索索引会更快,建议对多数量数据添加索引
objectStore.createIndex('name', 'name', {unique: false})
}
}
add() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME)
.add({id: 1, name: 'user1', age: 1, sex: 1})
request.onsuccess = (e) => {
console.log('add data success', e)
}
request.onerror = (e) => {
console.log('add data error', e)
}
}
read() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME).getAll()
request.onerror = (e) => {
console.log('get data error',e)
}
request.onsuccess = (e) => {
if(!request.result) {
console.log('no data')
return
}
const result = request.result
console.log('data array ----->', result, e)
}
}
eachAll() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME).openCursor()
request.onsuccess = (e) => {
const cursor = e.target.result
if(!cursor) return
console.log(cursor, '<------cursor')
}
}
put() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME)
.put({id: 1, name: 'user-put1', age: 2, sex: 2})
request.onsuccess = (e) => {
console.log('put data success', e)
}
request.onerror = (e) => {
console.log('put data error', e)
}
}
delete() {
const request = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME)
.delete(1)
request.onsuccess = (e) => {
console.log('put data success', e)
}
request.onerror = (e) => {
console.log('put data error', e)
}
}
readIndexData() {
const index = this.db.transaction([APP_OBJECT_STORE_NAME], 'readwrite')
.objectStore(APP_OBJECT_STORE_NAME).index('name')
const request = index.get('user1')
request.onerror = (e) => {
console.log('get data error',e)
}
request.onsuccess = (e) => {
if(!request.result) {
console.log('no data')
return
}
const result = request.result
console.log('data array ----->', result, e)
}
}
}
总结
indexedDB是前端大数据存储的一个解决方案,在有多数据量或者大文件(web3d模型等)的使用场景下效果显著
推荐Dexie食用
我们在上文中也看到了,indexedDB的大部分API都是使用回调的方式来进行编写。这并不符合我们Promise回调的开发方式。有一款Dexie的插件就封装了indexedDB,使其更加便携好用。教程文章也在笔者栏目下,推荐观看