IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。
此特性在 Web Worker 中可用。
如大多数的 web 储存解决方案一样,IndexedDB 也遵守同源策略。因此当你在某个域名下操作储存数据的时候,你不能操作其他域名下的数据。
连接
调用 indexedDB.open() 方法来打开数据库,如果没有数据库就创建一个
window.indexedDB.open(name[, version | options])
- name:数据库名称
- version:指定数据库版本,当你想要更改数据库格式(比如增加对象存储,非增加记录),必须指定更高版本,通过 versionchange 来更改
- options:{version: 4, storage: "temporary"}, 指定版本以及是否要为 IndexedDB 使用永久(默认值)存储的存储值
let request = window.indexedDB.open('test', 1) 复制代码
open 返回一个IDBOpenDBRequest对象——触发与此请求相关的后续事件的请求对象。
连接数据库在一个单独的线程中进行,包括以下几个步骤:
- 指定数据库已经存在时间:
- 等待
versionchange
操作完成。 - 如果数据库已删除,那等着删除完成。
- 如果已有数据库升级给定的
version,中止操作并返回
类型为VersionError 的
DOMError
。 - 如果已有数据库版本记录给定的
version,触发一个
versionchange
操作。 - 如果数据库不存在,创建指定名称的数据库,将版本号设置为给定版本,如果给定版本号,则设置为1,并且没有对象存储。
- 创建数据库连接。
如果创建连接成功,则触发 suucess 事件
let request = window.indexedDB.open('test', 1) request.onsuccess = function (event) { console.log(request === event.target) console.log('IndexedDB 连接成功'); } 复制代码
event.target 其实就是 open 的返回值,也就是 request,这里最有用的是 request.result属性
所有的异步方法返回一个 request 对象。如果 request 对象成功执行了,结果可以通过 result 属性访问到,并且该 request 对象上会触发 success 事件。如果操作中有错误发生,一个 error 事件会触发,并且会通过 result 属性抛出一个异常。
let request = window.indexedDB.open('test', 1) request.result 复制代码
在状态未变为 done 之前获取 result 或者连接过程中发生错误会抛出错误
除了 onsuccess 之外还有 onerror 和 onupgradeneeded 事件,分别用于错误回调和初次连接数据库时初始化
request.onerror = function (error) { console.log(error); } request.onupgradeneeded = function (event) { const res = request.result; let objectStore; if (!res.objectStoreNames.contains('person')) { objectStore = res.createObjectStore('person', { keyPath: 'id' }); objectStore.createIndex('name', 'name') } console.log('person created'); } 复制代码
createObjectStore 方法用于定义数据库的 schama,这个过程只能在onupgradeneeded 中完成。createObjectStore(storeName, {keypath})
定义了表名和主键。
此外还有createIndex
用于创建索引列。
概念
在 IndexedDB 中有四个比较重要的概念,了解这些概念有助于我们学习使用 IndexedDB
- objectStore:IndexedDB没有表的概念,它只有仓库store的概念,大家可以把仓库理解为表即可(后续均用表来代称),即一个store是一张表;
- index:我们可以在创建store的时候同时创建索引,在后续对store进行查询的时候即可通过索引来筛选,给某个字段添加索引后,在后续插入数据的过成功,索引字段便不能为空;
- cursor:游标,用于遍历或迭代数据库中的多条记录,游标有一个源,指示需要遍历哪一个索引或者对象存储区。它在所属区间范围内有一个位置,根据记录健(存储字段)的顺序递增或递减方向移动。游标使应用程序能够异步处理在游标范围内的所有记录;
- transaction:IndexedDB支持事务,即对数据库进行操作时,只要失败了,都会回滚到最初始的状态,确保数据的一致性。
操作
使用 transaction 来创建事务,所有的表操作都必须提前创建事务
transaction(table, mode)
- table:要连接的表名,是一个数组
- mode:要创建的事务类型,可选值有
readwrite
、readonly
创建事务之后获通过 objectStore 来获取对象存储实例,获取到实例之后就可以对表进行增删改查的操作了。
objectStore(table)
- table:表名,需要在创建事务是添加到 tableList 中
add
新增一条表数据,如果新增一个已存在的主键则会被忽略
function add() { if (request.readyState === 'done') { const db = request.result db.transaction(['person'], 'readwrite') .objectStore('person') .add({ id: 1, name: 'king', age: 18 }) } } 复制代码
put
修改表数据
function put() { if (request.readyState === 'done') { const db = request.result db.transaction(['person'], 'readwrite') .objectStore('person') .put({ id: 1, name: 'king', age: 23 }) } } 复制代码
delete
删除一条表数据,删除数据时只需要传入主键的值即可
function del() { if (request.readyState === 'done') { const db = request.result db.transaction(['person'], 'readwrite') .objectStore('person') .delete(1) } } 复制代码
get
通过主键获取值,这里注意获取值时需要通过回调才能拿到数据,换句话说,只有触发 success 时才能拿到结果,因为操作是异步的
function get() { if (request.readyState === 'done') { const db = request.result const res = db.transaction(['person'], 'readwrite') .objectStore('person') .get(1) res.onsuccess = e => { console.log(res.result); } } } 复制代码
除了使用主键查询之外,还可以使用索引查询,会返回符合查询条件的第一条数据
function index() { if (request.readyState === 'done') { const db = request.result const res = db.transaction(['person'], 'readwrite') .objectStore('person') .index('name') .get('king') res.onsuccess = e => { console.log(res.result); } } } 复制代码
getAll
根据索引查询时除了可以获取一条数据,还可以使用 getAll 来通过索引批量获取数据, getAll 还可以控制查询的数据条数
getAll(query, count)
count 最大支持2^32 - 1,最小支持 0
function getAll() { if (request.readyState === 'done') { const db = request.result const res = db.transaction(['person'], 'readwrite') .objectStore('person') .index('name') .getAll('king', 2) res.onsuccess = e => { console.log(res.result); } } } 复制代码
cursor
除了上面的两种查询方式,还可已使用游标的方式来进行查询。游标查询可以通过 continue 方法一直顺序遍历知道不再调用 continue。
function cursor() { if (request.readyState === 'done') { const db = request.result const res = db.transaction(['person'], 'readwrite') .objectStore('person') .openCursor() res.onsuccess = e => { const cursor = e.target.result // ===res.result if (cursor) { console.log(cursor.value); cursor.continue() } } } } 复制代码
游标查询也可使用索引的方式来查询指定的数据集,通过 IDBKeyRange.only(indexValue)
来过滤指定的索引值
function cursorIndex() { if (request.readyState === 'done') { const db = request.result const res = db.transaction(['person'], 'readwrite') .objectStore('person') .index('name') .openCursor(IDBKeyRange.only('king')) res.onsuccess = e => { const cursor = e.target.result if (cursor) { console.log(cursor.value); cursor.continue() } } } } 复制代码
可以利用游标进行更新和删除,直接在cursor 对象上直接调用
delete()
和update(value)
即可
分页查询
游标对象提供了一个 advance(count)
方法用于跳过count 个数据,然后配合计数即可完成分页功能
function cursorPage(page, size) { if (request.readyState === 'done') { const db = request.result const res = db.transaction(['person'], 'readwrite') .objectStore('person') .openCursor() let advance = true let count = 0 res.onsuccess = e => { const cursor = e.target.result if (advance) { advance = false cursor.advance((page - 1) * 2) return } if (cursor) { console.log(cursor.value); count++ if (count < size) { cursor.continue() } } } } } 复制代码
同理,该方法可应用于索引+游标查询分页
关闭链接
function closeDB(db) { db.close(); console.log("数据库已关闭"); } 复制代码
更多方法可以看一下 MDN