IndexedDB

背景与痛点

现在浏览器功能越发强大,所以很多开发者都希望将数据存在客户端浏览器,直接从本地获取数据,从而减少服务器请求获取数据。

而目前的情况是:

|产品|存储大小|描述| |:-|:-| |Cookie| <= 4KB |,每次请求都会发送给服务器| |LocalStorage|2.5 MB ~ 10 MB|各家浏览器不同,不支持搜索,不能建立索引|

介绍

基于上述痛点,解决方案就是 IndexedDB,它是浏览器提供的本地数据库,可以创建和操作。

功能

  • 允许存储大量数据,大于 250 MB。
  • 可直接查找接口。
  • 可建立索引。
  • 数据库类型更接近 NoSQL 数据库。

特点

  • 键值对存储。内部采用对象仓库(object store),所用类型数据都可以直接存入,包括二进制数据(ArrayBuffer 对象和 Blob 对象)。对象仓库中,数据是以“键值对”形式存在,每条数据都有对应唯一主键。
  • 存储空间大。一般来说不少于 250MB,甚至没有上限。
  • 异步。异步设计是为了防止大量数据的读写拖慢网页,这和 LocalStorage 同步方式不同,不会锁死浏览器。
  • 支持事务。意思就是如果一系列操作步骤有一环节失败,那整个事务都会取消,数据库将回滚到事务发生前的状态,不存在说改写部分数据。
  • 同源限制。也受同源限制,网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

概念

数据库 IDBDatabase

我们知道数据库是一系列相关数据的容器。每个域名可以创建任意多个数据库。IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。

对象仓库 IDBObjectStore

每个数据库包含多个对象仓库(object store),保存的是数据记录,类似于关系型数据库的表格。

数据记录

数据记录类似于关系型数据库的行,只有主键和数据体两部分。

  • 主键用来建立默认的索引,必须是不同的,否则会报错。
  • 主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数。
  • 数据体可以是任意数据类型,不限于对象。

索引 IDBIndex

数据检索是为了加快查找数据速度,所以可以在对象仓库里为不同的属性建立索引。

事务 IDBTransaction

数据记录的增删改查,要通过事务完成。事务对象提供 error、abort 和 complete 三个事件,用来监听操作结果。

使用

打开数据库

const request = window.indexedDB.open(databaseName, version);
1
  • indexedDB.open() 方法返回一个 IDBRequest 对象,该对象有个三个事件。
    • error,打开数据库失败。
    • success,打开数据库成功。
    • upgradeneeded,数据库升级,若指定的版本号,大于数据库的实际版本号,就会执行。
  • databaseName,数据库名。如果指定的数据库名不存在,就创建一个数据库。
  • version,数据库的版本,整数。如果省略,打开已有数据库时,默认为当前版本。创建数据库时,默认为 1。

创建对象仓库(数据仓库/表)

数据库创建后,紧接着就是创建对象仓库。这个操作是在上面事件 upgradeneeded 监听函数里面完成。

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // 判断表名是否存在
  if (!db.objectStoreNames.contains('objectStoreName')) {
    // 创建表
    db.createObjectStore('objectStoreName', { keyPath: 'id' });
  }
}
1
2
3
4
5
6
7
8
  • 判断对象仓库名 objectStoreName 是否存在。
  • 创建对象仓库 objectStoreName
  • 主键 keyPathid
    • 主键(key)是默认建立索引的属性。
    • 数据记录有可以作为主键的那就直接指定。
    • 数据记录的主键可以指定下一次对象属性。
    • 数据记录没有适合做主键的可自动生成 { autoIncrement: true }

创建索引

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // 判断表名是否存在
  if (!db.objectStoreNames.contains('objectStoreName')) {
    // 创建表
    const objectStore = db.createObjectStore('objectStoreName', { keyPath: 'id' });

    // 创建索引
    objectStore.createIndex('name', 'userName', { unique: false });
    objectStore.createIndex('phone', 'userPhone', { unique: true });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
  • name,索引名称。
  • userName,索引所在的属性。
  • { unique: false},配置对象。

新增数据

即向对象仓库写入数据记录,需要通过事务完成。

const addData = () => {
  // 新建一个事务。
  const transaction = db.transaction(['objectStoreName'], 'readwrite');
  const objectStore = transaction.objectStore('objectStoreName')
  const request = objectStore.add({ id: 1, userName: '江湖再见', userPhone: '137****3763' });

  request.onsuccess = function (event) {
    console.log('数据写入成功');
  };

  request.onerror = function (event) {
    console.log('数据写入失败');
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 通过 db.transaction 新建事务,指定对象仓库和操作模式。
  • 通过 transaction.objectStore 获得对象仓库。
  • 通过 objectStore.add 向对象仓库写入记录,这是一个异步操作,所以监听 successerror 事件判断是否写入成功。

读取数据

也是通过事务完成。

const readData = () => {
   var transaction = db.transaction(['objectStoreName']);
   var objectStore = transaction.objectStore('objectStoreName');
   var request = objectStore.get(1);

   request.onerror = function(event) {
     console.log('数据获取失败');
   };

   request.onsuccess = function(event) {
      if (request.result) {
        console.log('userName: ' + request.result.userName);
        console.log('userPhone: ' + request.result.userPhone);
      } else {
        console.log('数据为空');
      }
   };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • objectStore.get 通过主键参数获取数据。

遍历数据

遍历数据仓库的所有数据。

const readAllData = () => {
  var objectStore = db.transaction('objectStoreName').objectStore('objectStoreName');

   objectStore.openCursor().onsuccess = function (event) {
     var cursor = event.target.result;

     if (cursor) {
       console.log('Id: ' + cursor.key);
       console.log('Name: ' + cursor.value.name);
       console.log('Age: ' + cursor.value.age);
       console.log('Email: ' + cursor.value.email);
       cursor.continue();
    } else {
      console.log('没有更多数据了!');
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17