IndexedDB

基本概念

一个基于浏览器的key-value 数据库。

使用情景

  1. 需存储大量数据
  2. 需额外“加工”数据

特点

  1. 支持事务
  2. 异步API——注意使用 promise / generator / async 等封装
    • 虽然提供了同步API,但浏览器支持不好(最好和webworkers结合使用)。

基本使用过程

  1. 打开数据库。
  2. 创建对象仓库(object store)。
  3. 创建事务,执行一些数据库操作(增删改查)。
  4. 监听事件,等待数据库操作完成。
  5. 拿到操作结果。

存储概念模型

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
12
range值可为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
4
next : 主键值升序,主键值相等的数据都被读取
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
2
3
createIndex(indexName, property, {unique: false })
var index = store.index(indexName);
var data = index.get(indexNameValue)

多个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
34
const 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

事务的三种操作模式:

  1. 只读:readonly,可并发执行
  2. 读写:readwrite
  3. 版本变更:verionchange

事务的返回结果:

1
2
3
4
5
6
7
8
var transaction=db.transaction(['students','teacher'], 'readwrite');
var objectStore=transaction.objectStore('students'); //获取students数据

// 对数据进行操作

transaction.oncomplete(() => {});
transaction.onerror(() => {});
transaction.onabort(() => {});

版本更新

版本更新原理

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(结束)

注意事项

  1. 遵循“同源策略same-origin”(不能跨域)。即存储的数据只能在当前域名或其子域下使用
  2. 浏览器“隐私模式/无痕浏览”下不可用。
  3. 用户手动清除浏览器缓存时,会清除IndexedDB相关数据。
  4. 储存空间:
浏览器 限制
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封装插件

idb: IndexedDB Promised

其他存储类型

内存数据

变量存储在内存中,是常见的内存数据。

在react+ redux框架中,常见变量存储机制有props, state。当全局变量(reducer props)较多时,导致内存占用过多、系统变慢。这就要求前端开发人员合理分配props和state

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提高性能的常见用法(节省网络传送数据、提升首屏性能):

  1. 网站首次加载时从服务端下发样式和脚本代码。
  2. 将样式和脚本代码缓存到localStoage里。
  3. 下次再渲染时,直接从localStorage里读取。

通常,每个域名的localStorage使用量在2~10Mb之间。(不同浏览器不同)

localStorage只要不手工清除,就会一直存在。

在无痕浏览/隐私模式下localStorage不可用。

各个tab页直接可通讯。

sessionStorage

当前会话(session)关闭后,sessionStorage会默认被清除。
其他同localStorage。

Web SQL

废弃

参考链接

MDN说明文档

How to use IndexedDB to build Progressive Web Apps

Searching in Your IndexedDB Database

idb使用:DIVE INTO INDEXEDDB

-------------Keep It Simple Stupid-------------
0%