基本概念
一个基于浏览器的key-value 数据库。
使用情景:
- 需存储大量数据
- 需额外“加工”数据
特点:
- 支持事务
- 异步API——注意使用 promise / generator / async 等封装
- 虽然提供了同步API,但浏览器支持不好(最好和webworkers结合使用)。
基本使用过程:
- 打开数据库。
- 创建对象仓库(object store)。
- 创建事务,执行一些数据库操作(增删改查)。
- 监听事件,等待数据库操作完成。
- 拿到操作结果。
存储概念模型:
graph TD A(database /数据库/) --- B(object stores /表/) B --- C(object store /行/) C --- E(keyPath 或 autoIncrement /主键/) C --- D(index /索引/) C --- F(值) D -.源自.- F F --- F1(string) F --- F4(array) F --- F6(blob) F --- F7(file) F --- F5(date) F --- F2(number) F --- F3(object)
基本方法/属性
graph TD A1(IDBFactory) ==> B1(open) B1 ==> A2(IDBRequest) A2 -.- B21(onerror) A2 -.- B22(onsuccess) A2 -.- B23(onupgradeneeded) B22 ==> B2(success & event.target.result) B23 ==> B2 B2 ==> A3(IDBDatabase) A3 ==> B3(createObjectStore) B3 ==> A4(IDBObjectStore) A3 ==> B4(transaction) B4 ==> A5(IDBTransaction) A5 ==> B5(objectStore) B5 ==> A4 A4 ==> B6(index) B6 ==> A6(IDBIndex) A4 ==> B7(openCursor) B7 ==> A7(IDBCursor) style A1 fill: orange style A2 fill: orange style A3 fill: orange style A4 fill: orange style A5 fill: orange style A6 fill: orange style A7 fill: orange
objectStore
属性
主键
键类型 | 存储数据 | 示例 |
---|---|---|
不使用 | 任意类型的值。但每添加一条数据,都需额外指定“键” | db.createObjectStore(‘students’) |
keyPath | Javascript对象,且对象必须有一属性与keyPath同名。 | db.createObjectStore(‘students’,{keyPath:”id”}); |
keyGenerator | 任意类型的值。会自动生成键。 | db.createObjectStore(‘students’,{autoIncrement: true}); |
都使用 | Javascript对象。如果对象中有keyPath指定的属性则不生成新的键值。 | db.createObjectStore(‘students’,{keyPath:”id”, autoIncrement: true}); |
方法
方法应和事务结合使用。
方法 | 备注 |
---|---|
add(data) | 增加数据。 |
put(data, key?) | 增加 / 修改数据 |
get(key) | 获取数据。 |
getAll() | |
getAll(query) | A key or IDBKeyRange to be queried |
getAll(query, count) | |
getAllKeys() 参数同上 | |
count(key?) | match the provided key or IDBKeyRange |
delete(key) | 删除数据。 |
clear() | 清空全部objectStore数据。 |
add 和 put 的作用类似,区别在于 put 保存数据时,如果该数据的主键在数据库中已经有相同主键的时候,则会修改数据库中对应主键的对象,而使用 add 保存数据,如果该主键已经存在,则保存失败。
游标-Cursor
游标,可获取区间数据。
- openCursor(range, next) 获取符合条件的整个对象
- openKeyCursor(range, next) 获取符合条件的记录的那个keyPath值
1 | 不传参,则获取object store所有记录 |
其中range的取值如下:1
2
3
4
5
6
7
8
9
10
11
12range值可为null
// 第三个参数为true,则不包含最小键值;第四参数为true,则不包含最大键值。默认都为false
var boundRange = IDBKeyRange.bound('bill', 'donna', false, false);
var onlyRange = IDBKeyRange.only('donna');
// 第二个参数可选,为true则表示不包含最小主键,默认为false
var lowerRange = IDBKeyRange.lowerBound(1, false);
// 第二个参数可选,为true则表示不包含最大主键,默认为false
var upperRange = IDBKeyRange.upperBound(10, false);
next的取值如下:1
2
3
4next : 主键值升序,主键值相等的数据都被读取
nextunique : 主键值升序,主键值相等只读取第一条数据
prev : 主键值降序,主键值相等的数据都被读取
prevunique : 主键值降序,主键值相等只读取第一条数据
拿到cursor数据后,可进行如下操作:1
2
3
4
5
6
7
8
9
10
11
12
13
14// 更新数据
cursor.update({
userId : cursor.key,
userName : 'Hello',
age : 18
});
// 删除数据
cursor.delete();
// 继续读取下一条
cursor.continue();
cursor.value
cursor.primaryKey
索引-Index
1 | createIndex(indexName, property, {unique: false }) |
多个store,游标和索引示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34const request = window.indexedDB.open("database", 1);
request.onsuccess = () => {
const db = request.result;
const transaction = db.transaction(
['invoices', 'invoice-items'],
'readwrite'
);
const invStore = transaction.objectStore('invoices');
const invItemStore = transaction.objectStore('invoice-items');
// Get invoice cursor
const invoiceCursorRequest = invStore.index('VendorIndex')
.openCursor(IDBKeyRange.only('Frigidaire'));
invoiceCursorRequest.onsuccess = e => {
const invCursor = e.target.result;
if (invCursor) {
// Get invoice item cursor
const invItemCursorRequest = invItemStore
.index('InvoiceIndex')
.openCursor(
IDBKeyRange.only(invCursor.value.invoiceId)
);
invItemCursorRequest.onsuccess = e => {
const invItemCursor = e.target.result;
if (invItemCursor) {
invItemCursor.delete();
invItemCursor.continue();
}
}
invCursor.delete();
invCursor.continue();
}
}
};
事务
进行数据操作都需要事务。
事务定义:db.transaction(arg1, arg2)
- arg1:objectStoreName的字符串 或 objectStoreName字符串数组。
- arg2 : readonly,readwrite,verionchange
事务的三种操作模式:
- 只读:readonly,可并发执行
- 读写:readwrite
- 版本变更:verionchange
事务的返回结果:
1 | var transaction=db.transaction(['students','teacher'], 'readwrite'); |
版本更新
版本更新原理
graph LR A[Open request] --> B{Version higher?} B -->|Yes| C[Upgrade] B -->|No| D[Open]
查询慢的解决方案
如果数据量过大,indexedDB查询很慢,可在indexedDB之上, 再建索引——将key存储在localStorage中。
注意:需要做好key的同步增删工作。
graph LR A(查询) --Key--- B(localStorage/只存keys/) B --- C{存在Key?} C --- | yes | D(查询indexedDB) C --- | no| E(结束)
注意事项
- 遵循“同源策略same-origin”(不能跨域)。即存储的数据只能在当前域名或其子域下使用
- 浏览器“隐私模式/无痕浏览”下不可用。
- 用户手动清除浏览器缓存时,会清除IndexedDB相关数据。
- 储存空间:
浏览器 | 限制 |
---|---|
Chrome | 可用空间 <20% |
Firebox | 没有限制。会针对超过50MB(可在 dom.indexedDB.warningQuota 首选项自定义)的Blob请求权限 |
Safari | < 50MB |
IE10 | < 250MB |
浏览器 | 自动逐出政策 |
---|---|
Chrome | 在 Chrome 耗尽空间后采用 LRU 策略 |
Firebox | 在整个磁盘已装满时采用 LRU 策略 |
Safari | 无逐出 |
Edge | 无逐出 |
LRU策略:
- 当可用磁盘填满,磁盘容量管理器将会根据 LRU 策略清理数据 。
- LRU(Least Recently Used): 在内存中,最近最少使用的数据块将会被优先删除,直到浏览器不再超过限额。
npm封装插件
其他存储类型
内存数据
变量存储在内存中,是常见的内存数据。
在react+ redux框架中,常见变量存储机制有props, state。当全局变量(reducer props)较多时,导致内存占用过多、系统变慢。这就要求前端开发人员合理分配props和state。
Cookie
cookie属性: key , value, domain, path, expires, secure 和 httpOnly。
- httpOnly : 默认false。若改成true,则只能服务端读取,前端无法读取。
- secure : 默认false。若改成true,请求服务端时除非是https,否则服务端无法读取。
在cookie中查看有效期expires较为困难:
- 调试时,通过调试工具可查看
- 代码中获取时,需在cookie值(key, value)中保存过期时间,或另建cookie,专门保存。
能存储数据极少,大小4KB。
每次发送HTTP请求,相应域下的Cookie都会在header里传动到服务端,Cookie多了会增加数据传输的量,对性能造成影响。注意:fetch默认不携带cookie。
localStorage
只在前端读取。
localStorage提高性能的常见用法(节省网络传送数据、提升首屏性能):
- 网站首次加载时从服务端下发样式和脚本代码。
- 将样式和脚本代码缓存到localStoage里。
- 下次再渲染时,直接从localStorage里读取。
通常,每个域名的localStorage使用量在2~10Mb之间。(不同浏览器不同)
localStorage只要不手工清除,就会一直存在。
在无痕浏览/隐私模式下localStorage不可用。
各个tab页直接可通讯。
sessionStorage
当前会话(session)关闭后,sessionStorage会默认被清除。
其他同localStorage。
Web SQL
废弃
参考链接
How to use IndexedDB to build Progressive Web Apps