前端本地储存--IndexedDB
2021 年,如果你的前端应用,需要在浏览器上保存数据,有三个主流方案:
Cookie
Web Storage (LocalStorage)
IndexedDB
前面两个已经很熟悉了就不多赘述
转自:https://mp.weixin.qq.com/s/_DqXEfIrdHhy1z7ck7jwBw
IndexedDB
IndexedDB 的全称是 Indexed Database,从名字中就可以看出,它是一个数据库
IndexedDB 早在 2009 年就有了第一次提案,但其实它和 Web Storage 几乎是同一时间普及到各大浏览器的(没错,就是 2015 年那会,es6 也是那时候)
IndexedDB 是一个正经的数据库,它在问世后替代了原来不正经的 Web SQL 方案,成为了当今唯一运行在浏览器里的数据库
在我看来,IndexedDB 其实更适合当作终极前端本地数据储存方案
相比于 LocalStorage,IndexedDB 的优点是
储存量理论上没有上限
Chrome 对 IndexedDB 储存空间限制的定义是:硬盘可用空间的三分之一
所有操作都是异步的,相比 LocalStorage 同步操作性能更高,尤其是数据量较大时
原生支持储存 JS 的对象
是个正经的数据库,意味着数据库能干的事它都能干
但是缺点也比较致命:
操作非常繁琐
本身有一定门槛(需要你懂数据库的概念)
由于提案较早,IndexedDB 的 API 设计其实是比较糟糕的,对于新手而言,光是想连上数据库,并往里面加东西,都需要折腾半天
对于简单的数据储存而言,IndexedDB 的 API 显得太复杂了,再加上其 API 全是异步的,会带来额外的心智负担,远没有 LocalStorage 简单两行代码搞定数据存取来的快
因此,IndexedDB 在今天的使用规模相比 LocalStorage 差远了,即使 IndexedDB 本身的设计其实更适合用来在浏览器上储存数据
总之,如果不考虑 IndexedDB 的操作难度,其作为一个前端本地储存方案其实是接近完美的
简单理解数据库
在使用 IndexedDB 前,你首先需要懂基本的数据库概念
这里用 Excel 类比,简单介绍数据库的基本概念,不做太深入的讨论
需要了解四个基本概念,以关系型数据库为例
数据库 Database
数据表 Table(IndexedDB 中叫 ObjectStore)
字段 Field
事务 Transaction
(虽然 IndexedDB 算不上关系型数据库,但概念都是相通的)
假设清华和北大各自需要建一个数据库,用来存各自学生与教工的信息,假设命名为
清华:thu
北大:pku
这样,清北之间的数据就可以相互独立
然后,我们再到数据库里建表
student 表,储存学生信息
stuff 表,储存教工信息
数据表(Table)是什么?说白了,就是一个类似于 Excel 表一样的东西
比如 student 表,可以长这样:
image.png
上面的 学号、姓名、年龄、专业 就是数据表的字段
当我们想往 student 表添加数据时,就需要按照规定的格式,往表里加数据(关系型数据库的特点,而 IndexedDB 允许不遵守格式)
数据库也给我们提供了方法,当我们知道一个学生的学号(id),就可以在非常短的时间内,在表里成千上万个学生中,快速找到这个学生,并返回他的完整信息
也可以根据 id 定位,对该学生的数据进行修改,或者删除
id 这种每条数据唯一的值,就可以被用来做主键(primary key),主键在表内独一无二,无法添加相同主键的数据
而主键一般会被建立索引,所谓对字段建立索引,就是可以根据这个字段的值,在表里非常快速的找到对应的数据(通常不高于 O(logN)),如果没有索引,那可能就需要遍历整个表(O(N))
增、删、改、查这些操作,都需要通过事务 Transaction 来完成
如果事务中任何一个操作没有成功,整个事务都会回滚
在事务完成之前,操作不会影响数据库
不同事务之间不能互相影响
举个例子,当你发起一个事务,想利用这个事务添加两个学生,如果第一个学生添加成功,但是第二个学生添加失败,事务就会回滚,第一个学生将根本不会在数据库中出现过
事务在银行转账这种场景非常有用:如果转账中任何一步失败了,整个转账操作就和没发生过一样,不会造成任何影响
在同一个 Excel 文件(数据库)中,我们除了 student 表,还可以有 stuff 表(同一个数据库中有了两个不同的数据表):
image.png
如果你是新手,用 Excel 类比理解数据库完全没问题,足以使用 IndexedDB 了
虽然说 IndexedDB 使用 key-value 的模式储存数据,但你也完全可以用数据表 Table 的模式来看待它
IndexedDB 的使用
使用 IndexedDB 的第一步是打开数据库:
const request = window.indexedDB.open('pku');
上面这个操作打开了名为 pku 的数据库,如果不存在,浏览器会自动创建
然后 request 上有三个事件:
var db; // 全局 IndexedDB 数据库实例
request.onupgradeneeded = function (event) {
db = event.target.result;
console.log('version change');
};
request.onsuccess = function (event) {
db = request.result;
console.log('db connected')l;
};
request.onblocked = function (event) {
console.log('db request blocked!')
}
request.onerror = function (event) {
console.log('error!');
};
IndexedDB 有一个版本(version)的概念,连接数据库时就可以指定版本
const version = 1;
const request = window.indexedDB.open('pku', version);
版本主要用来控制数据库的结构,当数据库结构(表结构)发生变化时,版本也会变化
如上,request 上有四个事件:
onupgradeneeded 在版本改变时触发
注意首次连接数据库时,版本从 0 变成 1,因此也会触发,且先于 onsuccess
onsuccess 在连接成功后触发
onerror 在连接失败时触发
onblocked 在连接被阻止的时候触发,比如打开版本低于当前存在的版本
注意这四个事件都是异步的,意味着在连接 IndexedDB 的请求发出去后,需要过一段时间才能连上数据库,并进行操作
开发者对数据库的所有操作,都得放在异步连上数据库之后,这有的时候会带来很大的不便
而开发者如果想创建数据表(在 IndexedDB 里面叫做 ObjectStore),只能将其放到 onupgradeneeded 事件中(官方的定义是需要一个 IDBVersionChange 的事件)
request.onupgradeneeded = function (event) {
db = event.target.result;
if (!db.objectStoreNames.contains('student')) {
db.createObjectStore('student', {
keyPath: 'id', // 主键
autoIncrement: true // 自增
});
}
}
上面这段代码,在数据库初始化时,创建了一个 student 的表,并且以 id 为自增主键(每加一条数据,主键会自动增长,无需开发者指定)
在这一切做好以后,终于,我们可以连接数据库,然后添加数据了
const adding = db.transaction('student', 'readwrite') // 创建事务
.objectStore('student') // 指定 student 表
.add({ name: 'luke', age: 22 });
adding.onsuccess = function (event) {
console.log('write success');
};
adding.onerror = function (event) {
console.log('write failed');
}
用同样的方法再加一条数据
db.transaction('student', 'readwrite')
.objectStore('student')
.add({ name: 'elaine', age: 23 });
然后,打开浏览器的开发者工具,我们就能看到添加的数据:
image.png
这里可以看到 IndexedDB 的 key-value 储存特性,key 就是主键(这里指定主键为 id),value 就是剩下的字段和对应的数据
这个 key-value 结构对应的 Table 结构如下:
image.png
如果要获取数据,需要一个 readonly 的 Transaction
const request = db.transaction('student', 'readonly')
.objectStore(this.name)
.get(2); // 获取 id 为 2 的数据
request.onsuccess = function (event) {
console.log(event.target.result) // { id: 2, name: 'elaine', age: 23 }
}
综上,哪怕只是想简单的往 IndexedDB 里增加和查询数据,都需要写一大堆代码,操作非常繁琐,一不小心还容易掉坑里
那么,有没有什么办法,能更优雅的使用 IndexedDB,在代码量减少的情况下,还能更好的发挥其实力呢?