前端数据库 indexedDB 简介

jujusharp  •  Jul 5, 2019 7:40:52 PM

原文地址


前端数据库 indexedDB 简介

作者 | Brilliant Open Web团队

编辑 | Brilliant Open Web团队

前面介绍到 Cache Storage 是基于键值对的方式缓存数据,是适用于存储和检索网络请求及响应的存储系统,不能提供搜索功能,不能建立自定义的索引。本文介绍的 indexedDB 是浏览器环境提供的本地数据库,取代了 WebSQL 作为 HTML5 的标准,允许存储大量的数据,提供查询接口,还能创建索引等等,适用于可离线的或者需要存储大量数据的 web app。

indexedDB 是一个在浏览器环境中的非关系型数据库,具有以下特点:

  • 每个域名下可以新建多个数据库,数据库具有唯一的名字和可变的版本号

  • 每个数据库包含多个 objectStore(对象仓库),objectStore 类似于关系型数据库中的表,创建和修改 objectStore 需要升级数据库版本号

  • objectStore 用于保存 javascript object,通过 index(索引)查询和遍历数据

  • 基于transaction(事务),所有数据操作都需要在事务中进行

  • api 基本是异步的,通过事件的回调处理

  • 遵循同源协议,只能访问同域中的数据库

indexedDB 的基本使用模式为:

  1. 打开数据库

  2. 创建/获取 objectStore

  3. 启动事务,操作数据库

  4. 监听相应事件,获得操作结果

  5. 处理操作结果

下面通过代码来介绍 indexedDB 的使用方法。

打开数据库

通过 indexedDB.open() 方法打开数据库,该方法接受两个参数,分别是数据库名和版本号,版本号默认是 1,改变版本的唯一方法是通过一个比当前版本号更高的值去打开数据库,这时会触发 upgradeneeded 事件。

和 indexedDB 其他异步方法相同,open() 需要我们在事件回调中执行相应的操作。

// 如果有 mydb 这个数据库,就直接打开// 如果没有,就会创建 mydb 数据库let request = window.indexedDB.open('mydb', 1)request.onsuccess = e => {  // 获取数据库实例  let db = e.target.result   console.log("连接数据库成功")})request.onerror = e => {  console.log("连接数据库失败")})request.onupgradeneeded = e => {  let db = e.target.result  console.log("数据库版本升级")  // 修改数据库结构}


创建 ObjectStore

objectStore 类似于关系型数据库中的表,存放着相关的所有数据。所谓的 “相关” 是指,这些 object 必须具备相同的一个属性名,也就是主键。所有 object 主键的值都是唯一的。

在 upgradeneeded 事件回调中使用 createObjectStore() 方法创建 objectStore,该方法接受两个参数:

  1. 仓库名,同一个数据库的仓库名不能重复

  2. 主键名keyPath以及主键是否自增autoIncrement,如果存入的某个 object 不存在主键,而 autoIncrement 为 false,那么就会报错,如果 autoIncrement 为 true,在没有主键的情况下,存入数据库的时候,会被自动添加上。

request.onupgradeneeded = e => {  let db = e.target.result  // 创建自增主键为 id 的对象仓库 mystore  db.createObjectStore('mystore', {keyPath: 'id', autoIncrement: true})}


创建索引

在 indexedDB 中也存在索引,但和关系型数据库中索引的作用不同,indexedDB 的索引被指定为数据的某个属性,用于数据检索。所以看起来 objectStore 的索引,等效于关系型数据库中的表的字段。

创建索引实际上是对 objectStore 进行修改,因此,只能在数据库的 upgradeneeded 事件中处理,使用 createIndex() 方法创建索引,该方法接受三个参数:

  1. object 的属性名,可以是单个或一组属性名

  2. 可选参数,包括 unique 和 multiEntry,unique 指定属性的值是否唯一,multiEntry 用于对属性值为数组的数据进行检索,如果为 true,则会检查数组中的每一个值,否则只检查整个数组。

request.onupgradeneeded = e => {  let db = e.target.result  // 注意这里应该进行判断是否已经存在这个 objectStore,在这里略过  let objectStore = db.createObjectStore(    'mystore',    {keyPath: 'id'}  )  // 创建 name 为索引  objectStore.createIndex('name', 'name', {unique: true})}


启动事务

在对数据进行操作前,需要启动事务。通过 db.transaction() 方法可以启动事务,该方法接受两个参数:

  1. objectStore 名称,可以是单个或数组,指定事务可以操作的 objectStore

  2. 事务模式,可取值为 readonly 和 readwrite,两者都可以从 objectStore 读取数据,但只有 readwrite 才能修改数据

request.onsuccess = e => {  let db = e.target.result  let transaction = db.transaction(    'myObjectStore',    'readonly'  )}


增删改查

和任何数据库一样,indexedDB 也是进行数据存储,并提供一些方式让开发者可以对数据进行查询、添加、删除、修改。当一个事务开始之后,在它的生命周期以内,可以对 objectStore 进行数据操作,下面会通过一个简单的示例对 indexedDB 的增删改查操作进行介绍。

  • 添加数据add(),传入一个 object,主键值不能是已存在的,否则会添加失败

  • 更新数据put(),传入一个 object,如果主键值已存在,则更新仓库中的 object,如果没有,则添加这个 object。需要注意的是,如果主键是 autoIncrement 的,参数 object 中没有主键,那么 put() 会自动增加主键,将 object 添加到仓库中

  • 获取数据 get(),传入一个主键值,可以获取对应的 object

  • 删除数据 delete(),传入一个主键值,可以从 objectStore 中删除对应的 object

request.onsuccess = e => {  let db = e.target.result  let transaction = db.transaction(    ['myObjectStore'],    'readwrite'  )  let objectStore = transaction.objectStore('myObjectStore')  // 获取主键值为 ‘100001’ 的数据  let getRequest = objectStore.get('100001')  getRequest.onsuccess = e => {    // 获取到的数据    let object = e.target.result  }  // 添加新数据  let addRequest = objectStore.add({    id: '100002',    name: 'Zhang Fei'  })  addRequest.onsuccess = e => {    console.log('写入成功!')  }  // 更新数据  let putRequest = objectStore.put({    id: '100002',    name: 'Zhang San'  })  putRequest.onsuccess = e => {    console.log('写入成功!')  }  // 删除数据  let delRequest = objectStore.delete('100001')  delRequest.onsuccess = e => {    console.log('删除成功')  }}


检索

indexedDB 使用游标来检索数据。游标是一个机制,无法把游标打印出来看,但是可以通过游标得到你当前操作的元素,换句话说,游标有着类似 next() 的 continue() 方法,可以用来移动游标到下一个位置。

通过 openCursor() 方法打开游标,该方法接受两个可选参数:

  1. 范围,使用 IDBKeyRange 对象的四种方法定义范围:

    • 上边界 upperBound(),如 IDBKeyRange.upperBound(10) 表示键值小于等于 10

    • 下边界 lowerBound(),如 IDBKeyRange.lowerBound(10) 表示键值大于等于 10

    • 上下边界 bound(),如 IDBKeyRange.bound(10, 20) 表示键值从 10 到 20

    • 单值 only(),如 IDBKeyRange.only(10) 表示键值为 10

  2. 方向,包括:

    • next,升序遍历

    • nextunique,升序遍历,对于重复数据只取第一条

    • prev,降序遍历,

    • prevunique,降序遍历,对于重复数据只取第一条

在对应的 onsuccess 回调中,cursor 对象有以下属性和方法:

  • value,游标当前所指的 object

  • continue(),让游标滑动到下一个 object,并再次触发 onsuccess

  • update(),更新数据

  • delete(),删除数据

request.onsuccess = e => {  let db = e.target.result  let transaction = db.transaction(    ['myObjectStore'],    'readonly'  )  let store = transaction.objectStore('myObjectStore')  // 使用 price 索引(需要已创建 price 索引)  let index = store.index('price')  // 检索 price 值从 100 到 200 的数据  let range = IDBKeyRange.bound(100, 200)  let cursorRequest = index.openCursor(range)  let results = []  cursorRequest.onsuccess = e => {    let cursor = e.target.result    if (cursor) {      results.push(cursor.value)      cursor.continue()    } else {      // 遍历之后的 object 数据列表的结果      console.log(results)    }  }}

总结

indexedDB 作为浏览器端的数据库,为 web app 提供了持久化存储数据的方法和丰富的查询能力,但是具有以下局限性:

  • 数据可能会被清除,比如退出隐私模式或者用户手动清除数据

  • 存储容量限制

  • 需要自行同步服务端数据

另外由于其基于事务和使用异步 api 的特点,回调和错误处理会比较麻烦与复杂,使用起来不太方便,因此建议先对进行封装,参考:https://lavas-project.github.io/pwa-book/chapter03/5-indexeddb.html#%E5%88%A9%E7%94%A8-indexeddb-%E5%AE%9E%E7%8E%B0-db-%E7%B1%BB,也可以使用开源库:https://github.com/dfahlander/Dexie.js/ 和 https://github.com/erikolson186/zangodb

本文内容主要来自开源书籍《PWA 应用实战》。该书由百度 Web 生态团队撰写与分享,记录了团队过去两年积累的 PWA 方面的经验,欢迎对 Web 和 PWA 有浓厚兴趣的读者加入我们,一起来维护这本书。

Brilliant Open Web 

BOW(Brilliant Open Web)团队,是一个专门的Web技术建设小组,致力于推动 Open Web 技术的发展,让Web重新成为开发者的首选。

BOW 关注前端,关注Web;剖析技术、分享实践;谈谈学习,也聊聊管理。

关注 OpenWeb开发者,让我们一起推动 OpenWeb技术的发展!

OpenWeb开发者

ID:BrilliantOpenWeb

技术连接世界,开放赢得未来

0 回复
暂时没有回复,你也许会成为第一个哦