IndexDB入坑指北| 8月更文挑战

1,750 阅读5分钟

背景

现有的浏览器数据储存方案,都不适合储存大量数据:Cookie 的大小不超过4KB,且每次请求都会发送回服务器;LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。

概括起来,需要一个存储容量大,支持搜索和自定义索引的前端存储方案,就产生了IndexDB。

IndexDB属于非关系型数据库。(不支持SQL查询)

为什么不选ls,ss,cookie

localStorage

localStorage 是 HTML5 标准中新加入的技术,大小一般为5MB.

主要用于保存一些用户在当前主机的一些不包含敏感信息配置,有些网站会将用户信息甚至token存到ls中,这是非常危险的。

  • 有效期:除非被清除,否则永久保存
  • 通信:仅在客户端(即浏览器)中保存,不参与和服务器的通信
  • 易用性:源生接口可以接受,亦可再次封装来对Object和Array有更好的支持

sessionStorage

sessionStorage 与 localStorage 的接口类似,大小一般也为5MB,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空。

  • 有效期:仅在当前会话下有效,关闭页面或浏览器后被清除
  • 通信:仅在客户端(即浏览器)中保存,不参与和服务器的通信
  • 易用性:源生接口可以接受,亦可再次封装来对Object和Array有更好的支持

cookie

Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右。

它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的。

  • 有效期:一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效。
  • 通信:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题。
  • 易用性:需要程序员自己封装,源生的Cookie接口不友好

cookie后续将会被废弃。

indexDB特点

  • 键值对存储(主键必须是唯一的,否则会抛错)
  • 异步(不锁死浏览器)
  • 支持事务(一步失败,取消整个事务,回滚到之前的状态,避免只改了一部分)
  • 同源限制
  • 存储空间大,不少于250M,无上限
  • 支持二进制存储

核心概念

  • 数据库:IDBDatabase 对象,数据库有版本概念,同一时刻只能有一个版本,每个域名可以建多个数据库
  • 对象仓库:IDBObjectStore 对象,类似于关系型数据库的表格
  • 索引: IDBIndex 对象,可以在对象仓库中,为不同的属性建立索引,主键建立默认索引
  • 事务: IDBTransaction 对象,增删改查都需要通过事务来完成,事务对象提供了error,abord,complete三个回调方法,监听操作结果
  • 操作请求:IDBRequest 对象
  • 指针: IDBCursor 对象
  • 主键集合:IDBKeyRange 对象,主键是默认建立索引的属性,可以取当前层级的某个属性,也可以指定下一层对象的属性,还可以是一个递增的整数

操作流程

IndexDB.png

1.打开数据库

var request = window.indexedDB.open(databaseName, version); 

有三个回调方法:

  • request.onerror-返回错误信息
  • request.onsuccess-返回obj.target.result为数据库对象
  • request.onupgradeneeded-版本升级,同上

2.新建数据库

新建数据库和打开数据库是同一个操作,如果指定的数据库不存在,就会新建。

通常,新建数据库以后,第一件事就是创建对象仓库(即创建表),通常情况下会先判断是否已经创建这张表。

request.onupgradeneeded = function (event) {
  db = event.target.result;
  var objectStore;
  if (!db.objectStoreNames.contains('person')) {
    objectStore = db.createObjectStore('person', { keyPath: 'id' });
    // var objectStore = db.createObjectStore( 
    // 'person',
    // { autoIncrement: true } 
    //) 
  }
}

3.开启事务

增删改查都是通过事务实现的,新建事务需要指定表名和操作模式(读写/只读)。

封装创建事务的方法:

startTransaction (tableName, mode = 'readwrite') {
  //创建transaction有3个要求
  // 一、有connection(数据库连接)
  // 二、storeName(读取的store名)
  // 三、mode(包括readonly,readwrite和versionchange) 
  this.transaction = this.db.transaction(tableName, mode);
  this.transaction.oncomplete = () => console.log('transaction complete');
  this.transaction.onerror = e => console.log('error', e);
 }

事务在被创建的时候就会开启,而不是在请求的时候。

这里要考虑一个问题,如果同时对一个表的同一条数据做操作,是否存在数据覆盖或者丢失的问题。

4.增删改查

// 增 
let request = objectStore.add(data);
// 查 
let request = objectStore.get(id);
// 遍历 
function readAll() {
  var objectStore = db.transaction('person').objectStore('person');
  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('没有更多数据了!');
    }
  };
}
// 更新
let request = objectStore.put(obj);
// 删除
let request = objectStore.deletet(id);
// 通过索引查询
var store = transaction.objectStore('person');
var index = store.index('name');
var request = index.get('李四');

应用场景说明

这次狗子接入indexDB的原因是,实现了一个编辑器,需要对编写内容和采集图片进行本地化,本地化的原因,一方面是编辑器外面套了一个并不稳定的客户端,客户端卡死会导致内容丢失,很让人崩溃;另一方面避免了用户无刷新导致内容丢失;也是从断网断电这个角度考虑,如果可以不通过调用接口,实现实时保存,断电恢复后,内容也不会丢失。

因此,indexDB的推荐场景,我归纳大概是这样:

  • 需要大数据量的存储
  • 需要频繁的读写
  • 读写的内容不一定是规范化的数据库表结构
  • 希望读写过程是不影响主进程的

总而言之,indexDB是一个浏览器使用简易的数据库。随着前端功能复杂度提升,用户需要多元化,前端indexDB应用也就越来越多。桌面应用、Progressive Web App(PWA)、chrome扩展组件的开发等。用户同时会获取/操作更多的信息,怎么留存这些大量的数据,那么我们的indexDB就上线了。