前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PWA 系列(三)——IndexedDB

PWA 系列(三)——IndexedDB

作者头像
JS菌
发布2019-04-10 10:20:14
7190
发布2019-04-10 10:20:14
举报
文章被收录于专栏:JS菌JS菌

PWA 系列(三)——IndexedDB

IDB 操作的基本步骤是

  • open 方法打开数据库 ?
  • 然后是创建数据库 store 对象仓库 ?
  • 需要注意更新数据库版本应先调用 close 方法关闭旧版数据库
  • 需要注意创建 store 一定要在新版本数据库的 upgradeneeded 事件处理函数中创建,因为本质上他是修改数据库结构
  • 如果对数据库进行数据操作那么需要通过事务来执行 ?。

打开数据库

window.indexedDB 是一个 IDBFactory 对象,调用对象 open 方法返回的是一个 IDBOpenDBRequest 请求,监听 success 事件,e.target.result 指向一个名为 IDBDatabase 的对象,该对象就是连接到数据库的唯一 API。需要注意的是 IDBDatabase 对象有 close、createObjectStore、deleteObjectStore、transaction 方法和 name、version 等常用属性:

下面是个例子:

代码语言:javascript
复制
function getDB(dbName) { // 1️⃣
    return new Promise((resolve, reject) => {
        const request = window.indexedDB.open(dbName) // 2️⃣
        request.onerror = reject
        request.onsuccess = function (e) { // 3️⃣
            let db = e.target.result
            console.log(`db ${db.name}::${db.version} open success`)
            resolve(db)
        }
    })
}

步骤:

  • 第一步,需要先定义一个数据库这里通过自定义的 dbName 来创建这个新的数据库
  • 第二部,通过 open 方法打开数据库
  • 第三部,监听 onsuccess 方法,打开成功数据库对象就在这个 request 的 result 对象里面

使用方法:

代码语言:javascript
复制
; (async function () {
+   let db = await getDB('DEMO')
+   db.close()
})()

打开 Application 面板就能够看到这个数据库和存储空间了:

但目前没什么卵用 ? 因为没有定义数据库数据结构调用还不能存储东西,这个时候我们就要更新版本号并创建数据存储的结构了

升级版本号

代码语言:javascript
复制
function getUpgradedDB(db) { // 1️⃣
    return new Promise((resolve, reject) => {
        console.log(`current ${db.name}::${db.version}`)
        let request = window.indexedDB.open(db.name, ++db.version) // 2️⃣
        request.onerror = reject
        request.onupgradeneeded = function (e) { // 3️⃣
            let db = e.target.result
            console.log('db upgraded')
            resolve(db)
        }
    })
}

步骤:

  • 第一步,首先获取 db 对象,(可选,方便累加现有的数据库版本号)
  • 第二步,然后通过 open 方法传入新版本号来创建新的版本数据库( ⚠️ 注意版本号始终应当为整型数值)
  • 第三步,监听 upgradeneeded 事件,将 request 的 result 对象返回,这样我们就获得了新版本的数据库

使用方法:

代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
+   db.close()
+   db = await getUpgradedDB(db)
})()

⚠️️ 再次提醒如果传入 db 升级数据库,一定要先 close 方法关闭旧版本数据库

现在我们数据库的版本就升级一了,这个时候就应该在升级的同时创建数据库的结构:

创建存储空间

假定我们需要存储这样的数据:

代码语言:javascript
复制
{ name: 'oli', age: 12, email: 'example@example.com' }

需要注意的是:

  • name 为 name 键的索引 不唯一
  • age 为 age 键的索引 不唯一
  • email 为主键 唯一不重复

创建存储空间需要调用 db 数据库对象的 createObjectStore 方法;该方法返回的是一个 IDBObjectStore 对象。

存储空间有以下方法和属性:

看名字基本都能明白代表的含义,详细见文档:https://developer.mozilla.org/zh-CN/docs/Web/API/IDBObjectStore

代码语言:javascript
复制
function initDBStructor(db, storeName, opts, indexArr) { // 1️⃣
    return new Promise((resolve, reject) => {
        let store = db.createObjectStore(storeName, opts) // 2️⃣
        indexArr.forEach(indexObj => { // 3️⃣
            store.createIndex(indexObj.name, indexObj.name, { unique: indexObj.unique })
        })
        store.transaction.onerror = reject
        store.transaction.oncomplete = function (e) { // 4️⃣
            console.log('db initiated')
            resolve(e.target.db)
        }
    })
}

步骤

  • 首先,我们接收数据库作为参数,storeName 代表存储空间的名称,opts 参数是创建的存储空间的选项(比如设置主键等),indexArr 则代表数据库需要创建的索引(这里假定所有数据都需要创建索引)
  • 然后通过调用数据库的 createObjectStore 方法创建存储空间
  • 根据传入的数据结构使用 store 的 createIndex 创建索引
  • 监听 store 事务的 complete 方法,数据库初始化完成

使用方法:

代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
    db.close()
    db = await getUpgradedDB(db)
+   db = await initDBStructor(db, 'users', { keyPath: 'email' }, [{ name: 'name', unique: false }, { name: 'age', unique: false }, { name: 'email', unique: true }])
})()

根据数据,我们设置了主键为 email 字段,然后 name 和 age 字段 unique 为 false,email 字段 unique 属性为 true,设置完毕再去调试面板:

然后我们就可以对数据仓库进行操作了:

数据的增查改删

增加数据

代码语言:javascript
复制
function setItem(db, storeName, data) { // 1️⃣
    return new Promise((resolve, reject) => {
        let request = db.transaction(storeName, 'readwrite').objectStore(storeName).add(data) // 2️⃣
        request.onerror = reject
        request.onsuccess = function (e) { // 3️⃣
            console.log('add data success')
            resolve(e.target.result)
        }
    })
}

步骤:

  • 这个函数需要获取三个参数,一个是数据库对象 db,一个是 store 的名称,另外一个则是需要插入的数据
  • 然后调用 db 的 transaction,传入 store 名称并设置操作为读写权限,然后打开存储空间并调用 add 方法插入数据
  • 最后监听 request 的 success 事件

使用方法:

代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
-   db.close()
-   db = await getUpgradedDB(db)
+   let res = await setItem(db, 'users', { name: 'oli', age: 11, email: 'hello@example.com' })
})()

我们增加了一条数据,调试工具打开看下现在的数据库:

成功插入数据!值得庆祝 ?

查询数据

代码语言:javascript
复制
function getItem(db, storeName, key) { // 1️⃣
    return new Promise((resolve, reject) => {
        let request = db.transaction(storeName, 'readonly').objectStore(storeName).get(key) // 2️⃣
        request.onerror = reject
        request.onsuccess = function (e) { // 3️⃣
            console.log('get data success')
            resolve(e.target.result)
        }
    })
}

步骤:

  • 第一步,获取到 db 数据库对象,接收 store 名称以及数据 key 我们要根据 key 来做查询操作获取 value
  • 第二步,调用数据库对象的 transaction 传入 store 名称和只读权限,然后获取存储空间并调用 get 方法=
  • 第三步,监听 success 事件

使用方法:

代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
-   let res = await setItem(db, 'users', { name: 'oli', age: 11, email: 'hello@example.com' })
+   let res = await getItem(db, 'users', 'hello@example.com')
})()

返回的数据如下:

修改数据

代码语言:javascript
复制
function modifyItem(db, storeName, key, data) { // 1️⃣
    return new Promise(async (resolve, reject) => {
        let request = db.transaction(storeName, 'readonly').objectStore(storeName).get(key) // 2️⃣
        request.onerror = reject
        request.onsuccess = function (e) { // 3️⃣
            if (e.target.result) {
                let requestUpdate = db.transaction(storeName, 'readwrite').objectStore(storeName).put({ ...e.target.result, ...data }) // 4️⃣
                requestUpdate.onerror = reject
                requestUpdate.onsuccess = function (e) { // 6️⃣
                    console.log('modify item success')
                    resolve(e.target.result)
                }
            } else {
                reject('not match key')
            }
        }
    })
}

步骤:

  • 首先接收 db 数据库对象、store 存储空间名称以及 key 和修改后的 data
  • 然后根据 key 进行查询操作
  • 监听 request 的 success 事件
  • 然后调用 put 方法将查询的数据和修改后的数据 merge 并插入到存储空间(为了方便演示,直接合并对象)
  • 最后监听 requestUpdate 的 success 事件

数据修改成功:

代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
-   let res = await getItem(db, 'users', 'hello@example.com')
+   let res = await modifyItem(db, 'users', 'hello@example.com', { name: 'woooh' })
})()

效果如下:

删除数据

最后再来个删除数据:

代码语言:javascript
复制
function deleteItem(db, storeName, key) { // 1️⃣
    return new Promise((resolve, reject) => {
        let request = db.transaction(storeName, 'readwrite').objectStore(storeName).delete(key) // 2️⃣
        request.onerror = reject
        request.onsuccess = function (e) { // 3️⃣
            console.log('remove item success')
            resolve(e.target.result)
        }
    })
}

步骤:

  • 接收参数 db 数据库对象,store 仓库名称,key
  • 然后创建 readwrite 事务,调用存储空间的 delete 方法
  • 监听 request 的 success 事件

删除成功:

代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
-   let res = await getItem(db, 'users', 'hello@example.com')
+   let res = await deleteItem(db, 'users', 'hello@example.com')
})()

看看效果:

empty! ?

游标

使用游标 openCursor 返回的是一个 IDBCursor 对象,监听 success 返回的 cursor 则是含有 value 的 IDBCursorWithValue 对象,两者区别:

在使用游标之前,我们先插入几条假数据:

然后实现一个通过游标获取所有数据的函数:

代码语言:javascript
复制
function getAllValue(db, storeName) { // 1️⃣
    return new Promise((resolve, reject) => {
        let request = db.transaction(storeName).objectStore(storeName).openCursor() // 2️⃣
        let res = []
        request.onerror = reject
        request.onsuccess = function (e) { // 3️⃣
            let cursor = e.target.result
            if (cursor) { // 4️⃣
                res.push(cursor.value)
                cursor.continue()
            } else {
                resolve(res)
                console.log('get all value finish')
            }
        }
    })

}

步骤:

  • 首先函数接收两个参数,一个是 db 对象,一个是存储空间名称
  • 然后创建一个 request 调用存储空间的 openCursor 方法
  • 然后监听 request 的 success 事件
  • 检测是否存在 cursor,如果是则 push 数据并调用 continue 方法继续监听 openCursor 的 success 事件遍历,否则返回所有结果

使用方法:

代码语言:javascript
复制
; (async function () {
+   let db = await getDB('DEMO')
+   let res = await getAllValue(db, 'users')
})()

效果如下:

另外获得所有数据的数组还可以使用 getAll 方法:

代码语言:javascript
复制
function getAllData(db, storeName) {
    return new Promise((resolve, reject) => {
        let request = db.transaction(storeName).objectStore(storeName).getAll() // 1️⃣
        request.onerror = reject
        request.onsuccess = function (e) { // 2️⃣
            let data = e.target.result
            console.log('get all value finish')
            resolve(data)
        }
    })
}

步骤:

  • 使用存储空间上的 getAll 方法
  • 监听 success 事件

游标范围

另外还可以给游标 openCursor 方法传入 keyRange 来指定游标的范围,这种 keyRange 有多种类型,分别是 IDBKeyRange 的几个内置方法:

  • only 指定仅匹配
  • lowerBound 在…之下 另外接收一个 Boolean 指定是否包括当前 key
  • upperBound 在…之上 另外接收一个 Boolean 指定是否包括当前 key
  • bound 在…之间 另外接收两个 Boolean 指定是否包括当前两个 key

详细见下表:

代码语言:javascript
复制
function getDataByCursor(db, storeName, indexName, type, ...args) { // 1️⃣
    return new Promise((resolve, reject) => {
        let keyRange
        switch (type) { // 2️⃣
            case 'only':
                keyRange = IDBKeyRange.only(args[0])
                break
            case 'lowerBound':
                keyRange = IDBKeyRange.lowerBound(args[0], args[1])
                break
            case 'upperBound':
                keyRange = IDBKeyRange.upperBound(args[0], args[1])
                break
            case 'bound':
                keyRange = IDBKeyRange.bound(...args)
                break
            default:
                break
        }
        let request = db.transaction(storeName).objectStore(storeName).index(indexName).openCursor(keyRange) // 3️⃣
        let res = []
        request.onerror = reject
        request.onsuccess = function (e) { // 4️⃣
            let cursor = e.target.result
            if (cursor) {
                res.push(cursor.value)
                cursor.continue()
            } else {
                resolve(res)
                console.log('get all value finished')
            }
        }
    })
}

步骤:

  • 首先接收几个参数 db 数据库对象,store 存储空间名称,index 索引名称以及 范围类型名称和选项
  • 然后根据不同名称对应不同游标范围
  • 调用 openCursor 方法并传入游标范围参数
  • 监听 success 事件获取数据

使用方法:

代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
    let res
+   res = await getDataByCursor(db, 'users', 'name', 'only', 'oli')
+   res = await getDataByCursor(db, 'users', 'name', 'lowerBound', 'oli')
+   res = await getDataByCursor(db, 'users', 'name', 'lowerBound', 'oli', true)
+   res = await getDataByCursor(db, 'users', 'name', 'bound', 'oli', 'troy', false, false)
})()

另外还可以指定游标的方向,给 openCursor 方法传入第二个参数 prev 来指定使用倒序;如果想要过滤重复的记录,那么传入 IDBCursor.nextuniqueIDBCursor.prevunique 即可;其他参数见文档:https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor

使用索引搜索

index 方法返回一个名为 IDBIndex 的对象

IDBIndex 这个对象的属性方法见文档:https://developer.mozilla.org/zh-CN/docs/Web/API/IDBIndex

可通过索引搜索已经建立索引的条目:

代码语言:javascript
复制
function getDataByIndex(db, storeName, indexName, key) { // 1️⃣
    return new Promise((resolve, reject) => {
        let request = db.transaction(storeName).objectStore(storeName).index(indexName).get(key) // 2️⃣
        request.onerror = reject
        request.onsuccess = function(e) { // 3️⃣
            let data = e.target.result
            console.log('get value')
            resolve(data)
        }
    })
}
  • 首先接收参数 db 对象、store 存储空间名称、index 索引名称和搜索关键词
  • 然后调用存储空间的 index 方法的 get 方法搜索关键词
  • 然后监听 success request 的 result 值即为搜索到的数据
代码语言:javascript
复制
; (async function () {
    let db = await getDB('DEMO')
-   let res = await getAllData(db, 'users')
+   let res = await getDataByIndex(db, 'users', 'name', 'troy')
})()

另外还有在索引上调用的两种游标使用方法,见 MDN 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API/Using_IndexedDB#%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95

事务和请求之间的关系

最后来聊一聊 IDB 数据库的事务和请求之间的关系:

首先,请求就是所有针对 IDB 的异步操作都会返回请求,我们监听 success 才能在请求成功之后通过 result 对象获取到的结果

然后是事务,一个请求可能有事务也有可能没有事务,比方说只连接上数据库但没有操作数据,那么就存在请求,但不存在事务

The transaction for the request. This property can be null for certain requests, such as for request returned from IDBFactory.open (You're just connecting to a database, so there is no transaction to return).

举个例子:

代码语言:javascript
复制
; (async function () {
    let getIDBRequest = window.indexedDB.open('DEMO')
    let db
    getIDBRequest.onsuccess = e => {
        db = getIDBRequest.result
        debugger
    }
})()

打开数据库操作就是一个请求:

这里面就没有事务

一旦操作数据,那么你就必须首先创建一个事务并设置模式,然后通过存储空间来发起请求

代码语言:javascript
复制
; (async function () {
    let getIDBRequest = window.indexedDB.open('DEMO')
    let db
    getIDBRequest.onsuccess = e => {
        db = getIDBRequest.result
        let transaction = db.transaction('users', 'readonly')
        let store = transaction.objectStore('users')
        let request = store.count()
        debugger
    }
})()

那么这个请求也就会包含事务了:

PWA 系列第三章 IDB 的简要介绍就到这里,这个系列的下篇文章将介绍 Service Worker ?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JS菌 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • PWA 系列(三)——IndexedDB
    • 打开数据库
      • 升级版本号
        • 创建存储空间
          • 数据的增查改删
            • 游标
              • 游标范围
                • 使用索引搜索
                  • 事务和请求之间的关系
                  相关产品与服务
                  数据库
                  云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档