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对象——触发与此请求相关的后续事件的请求对象。

连接数据库在一个单独的线程中进行,包括以下几个步骤:

  1. 指定数据库已经存在时间:
    • 等待versionchange操作完成。
    • 如果数据库已删除,那等着删除完成。
  2. 如果已有数据库升级给定的version,中止操作并返回类型为VersionError 的 DOMError
  3. 如果已有数据库版本记录给定的version,触发一个 versionchange操作。
  4. 如果数据库不存在,创建指定名称的数据库,将版本号设置为给定版本,如果给定版本号,则设置为1,并且没有对象存储。
  5. 创建数据库连接。

如果创建连接成功,则触发 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属性

image-20220805172018354

所有的异步方法返回一个 request 对象。如果 request 对象成功执行了,结果可以通过 result 属性访问到,并且该 request 对象上会触发 success 事件。如果操作中有错误发生,一个 error 事件会触发,并且会通过 result 属性抛出一个异常。

let request = window.indexedDB.open('test', 1)
request.result

在状态未变为 done 之前获取 result 或者连接过程中发生错误会抛出错误

image-20220805173151226

除了 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用于创建索引列。

image-20220805184039187

概念

在 IndexedDB 中有四个比较重要的概念,了解这些概念有助于我们学习使用 IndexedDB

  • objectStore:IndexedDB没有表的概念,它只有仓库store的概念,大家可以把仓库理解为表即可(后续均用表来代称),即一个store是一张表;
  • index:我们可以在创建store的时候同时创建索引,在后续对store进行查询的时候即可通过索引来筛选,给某个字段添加索引后,在后续插入数据的过成功,索引字段便不能为空;
  • cursor:游标,用于遍历或迭代数据库中的多条记录,游标有一个源,指示需要遍历哪一个索引或者对象存储区。它在所属区间范围内有一个位置,根据记录健(存储字段)的顺序递增或递减方向移动。游标使应用程序能够异步处理在游标范围内的所有记录;
  • transaction:IndexedDB支持事务,即对数据库进行操作时,只要失败了,都会回滚到最初始的状态,确保数据的一致性。

操作

使用 transaction 来创建事务,所有的表操作都必须提前创建事务

transaction(table, mode)

  • table:要连接的表名,是一个数组
  • mode:要创建的事务类型,可选值有readwritereadonly

创建事务之后获通过 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


前端小白