新一代的前端存储方案--indexedDB

前端存储

  咱们都知道在前端开发当中,有时会由于某些需求,要将一些数据存储在前端本地当中.好比说:为了优化性能,将一些经常使用的数据存在本地,这样之后须要的时候直接从本地拿,不须要再向后端进行请求.还有就是为了防止CSRF攻击,后端给前端一个token,前端就须要将这个token存在本地.以后每次请求都须要带上这个token.等等不一而足.前端

  而这些需求就不油避免的造就一个前端的发展方向--前端存储web

  在前端的'上古时代'里,咱们前端想要存储数据,只有一种方式,那就是Cookie.可是Cookie虽然能够作前端存储方案,可是却也有着不少局限性.首先它的存储空间大小只有4K,其次它的存储有效时间有限制,而后存在Cookie中的数据,在你每次进行请求的时候都会将它带上.使得每次的请求数据都会无心义的增大.最后,也是最重要的一点.Cookie设计之初就不是就是让咱们前端存数据用的.它只是为了让网站验证用户身份用的.至于Cookie的本地存储功能只是它的一个手段而已.关于这点大家能够看下个人另一篇文章---在HTML5的时代,从新认识Cookie数据库

  综上所述,使用Cookie做为前端存储有这许多缺点,因此通过前端社区的不断努力,在HTML5中有了真正的前端存储方案Web Storage.它分为两种,一种是永久存储的localStorage,一种是会话期间存储的sessionStorage.对比Cookie,Web Storage的优点很明显:后端

  1. 存储空间更大,有5M大小
  2. 在浏览器发送请求是不会带上web Storage里的数据
  3. 更加友好的API
  4. 能够作永久存储(localStorage).

  这一切看起来很完美,可是随着前端的不断发展,web Storage也有了一些不太合适的地方:数组

  1. 随着web应用程序的不断发展,5M的存储大小对于一些大型的web应用程序来讲有些不够
  2. web Storage只能存储string类型的数据.对于Object类型的数据只能先用JSON.stringify()转换一下在存储.

  基于上述缘由,前端社区又提出了浏览器数据库存储这个概念.而Web SQL DatabaseindexedDB(索引数据库)是对这个概念的实现.其中Web SQL Database在目前来讲基本已经被放弃.因此目前主流的浏览器数据库的实现就是indexedDB(索引数据库).也就是咱们要介绍的 新一代的前端存储方案--indexedDB浏览器

什么是indexedDB

indexedDB的介绍

IndexedDB 是一种使用浏览器存储大量数据的方法.它创造的数据能够被查询,而且能够离线使用. IndexedDB对于那些须要存储大量数据,或者是须要离线使用的程序是很是有效的解决方法. --- MDNsession

indexedDB的概念

  使用IndexedDB,你能够存储或者获取数据,使用一个key索引的。 你能够在事务(transaction)中完成对数据的修改。和大多数web存储解决方案相同,indexedDB也听从同源协议(same-origin policy). 因此你只能访问同域中存储的数据,而不能访问其余域的。
  API包含异步(asynchronous) API 和同步(synchronous)API两种。 异步API适合大多数状况, 同步API必须同 WebWorkers一同使用. 目前,没有主流浏览器支持同步API。 即便同步API被支持了,你也会在大多数的状况使用异步API。
  IndexedDB 是 WebSQL 数据库的取代品, W3C组织在2010年11月18日废弃了webSql. IndexedDB 和WebSQL的不一样点在于WebSQL 是关系型数据库(复杂)IndexedDB 是key-value型数据库(简单好使).并发

  上面是MDN上对于IndexedDB的介绍.其简单而言,indexedDB就是一个基于事务操做的key-value型数前端数据库.其API大可能是异步的异步

indexedDB的使用

建立一个indexedDB数据库

const request = indexedDB.open('myDatabase', 1);

request.addEventListener('success', e => {
   console.log("链接数据库成功");
});

request.addEventListener('error', e => {
    console.log("链接数据库失败");
});
复制代码

  在上面代码中咱们使用indexedDB.open()建立一个indexedDB数据库.open()方法接受能够接受两个参数.第一个是数据库名,第二个是数据库的版本号.同时返回一个IDBOpenDBRequest对象用于操做数据库.其中对于open()的第一个参数数据库名,open()会先去查找本地是否已有这个数据库,若是有则直接将这个数据库返回,若是没有,则先建立这个数据库,再返回.对于第二个参数版本号,则是一个可选参数,若是不传,默认为1.但若是传入就必须是一个整数.async

  在经过对indexedDB.open()方法拿到一个数据库对象IDBOpenDBRequest咱们能够经过监听这个对象的success事件和error事件来执行相应的操做.

建立一个对象仓库

  再有了一个数据库以后,咱们获取就想要去存储数据了,可是单只有数据库还不够,咱们还须要有对象仓库(object store).对象仓库(object store)是indexedDB数据库的基础,其相似于MySQL中表的概念.

  要建立一个对象仓库必须在upgradeneeded事件中,而upgradeneeded事件只会在版本号更新的时候触发.这是由于indexedDB API中不容许数据库中的数据仓库在同一版本中发生变化

const request = indexedDB.open('myDatabase', 2);

request.addEventListener('upgradeneeded', e => {
    const db = e.target.result;
    const  store = db.createObjectStore('Users', {keyPath: 'userId', autoIncrement: false});
    console.log('建立对象仓库成功');
});
复制代码

  在上述代码中咱们监听upgradeneeded事件,并在这个事件触发时使用createObjectStore()方法建立了一个对象仓库.createObjectStore()方法接受两个参数,第一个是对象仓库的名字,在同一数据库中,仓库名不能重复.第二个是可选参数.用于指定数据的主键,以及是否自增主键.

建立事务

  OK如今咱们有了数据库和对象仓库了,咱们是否就能够存储数据了了.很抱歉,仍是不行.咱们还差最后同样东西----事务.

什么是事务

  一个数据库事务一般包含了一个序列的对数据库的读/写操做。它的存在包含有如下两个目的

  1. 为数据库操做序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即便在异常状态下仍能保持一致性的方法。
  2. 当多个应用程序在并发访问数据库时,能够在这些应用程序之间提供一个隔离方法,以防止彼此的操做互相干扰。

  并不是任意的对数据库的操做序列都是数据库事务。数据库事务拥有如下四个特性,习惯上被称之为ACID特性。

  • 原子性(Atomicity):事务做为一个总体被执行,包含在其中的对数据库的操做要么所有被执行,要么都不执行
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另外一个一致状态。一致状态的含义是数据库中的数据应知足完整性约束
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不该影响其余事务的执行
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中

  上面是维基百科上对数据库事务的解释.简单来讲事务就是用来保证数据库操做要么所有成功,要么所有失败的一个限制.好比,在修改多条数据时,前面几条已经成功了.,在中间的某一条是失败了.那么在这时,若是是基于事务的数据库操做,那么这时数据库就应该重置前面数据的修改,放弃后面的数据修改.直接返回错误,一条数据也不修改.

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');
});
复制代码

上述代码中咱们使用transaction()来建立一个事务.transaction()接受两个参数,第一个是你要操做的对象仓库名称,第二个是你建立的事务模式.传入 readonly时只能对对象仓库进行读操做,没法写操做.能够传入readwrite进行读写操做.

操做数据

  好了如今有了数据库,对象仓库,事务以后咱们终于能够存储数据了.

  • add() : 增长数据。接收一个参数,为须要保存到对象仓库中的对象。
  • put() : 增长或修改数据。接收一个参数,为须要保存到对象仓库中的对象。
  • get() : 获取数据。接收一个参数,为须要获取数据的主键值。
  • delete() : 删除数据。接收一个参数,为须要获取数据的主键值。

add 和 put 的做用相似,区别在于 put 保存数据时,若是该数据的主键在数据库中已经有相同主键的时候,则会修改数据库中对应主键的对象,而使用 add 保存数据,若是该主键已经存在,则保存失败。

添加数据

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    // 保存数据
    const reqAdd = store.add({'userId': 1, 'userName': '李白', 'age': 24});

    reqAdd.addEventListener('success', e => {
      console.log('保存成功')
    })
});
复制代码

获取数据

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    // 获取数据
    const reqGet = store.get(1);

    reqGet.addEventListener('success', e => {
      console.log(this.result.userName);    // 李白
    })
});
复制代码

删除数据

const request = indexedDB.open('myDatabase', 3);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    // 删除数据
    const reqDelete = store.delete(1);

    reqDelete.addEventListener('success', e => {
      console.log('删除数据成功');    // 李白
    })
});
复制代码

使用游标

  在上面当中咱们使用get()方法传入一个主键来获取数据,可是这样只可以获取到一条数据.若是咱们想要获取多条数据了怎么办.咱们可使用游标,来获取一个区间内的数据.

  要使用游标,咱们须要使用对象仓库上的openCursor()方法建立币打开.openCursor()方法接受两个参数.

openCursor(range?: IDBKeyRange | number | string | Date | IDBArrayKey, direction?: IDBCursorDirection): IDBRequest;
复制代码

  第一个是范围,范围能够是一个IDBKeyRange对象.用如下方式建立.

// boundRange 表示主键值从1到10(包含1和10)的集合。
// 若是第三个参数为true,则表示不包含最小键值1,若是第四参数为true,则表示不包含最大键值10,默认都为false
var boundRange = IDBKeyRange.bound(1, 10, false, false);

// onlyRange 表示由一个主键值的集合。only() 参数则为主键值,整数类型。
var onlyRange = IDBKeyRange.only(1);

// lowerRaneg 表示大于等于1的主键值的集合。
// 第二个参数可选,为true则表示不包含最小主键1,false则包含,默认为false
var lowerRange = IDBKeyRange.lowerBound(1, false);

// upperRange 表示小于等于10的主键值的集合。
// 第二个参数可选,为true则表示不包含最大主键10,false则包含,默认为false
var upperRange = IDBKeyRange.upperBound(10, false);
复制代码

  第二个参数是方向.主要有一下几种

  • next : 游标中的数据按主键值升序排列,主键值相等的数据都被读取
  • nextunique : 游标中的数据按主键值升序排列,主键值相等只读取第一条数据
  • prev : 游标中的数据按主键值降序排列,主键值相等的数据都被读取
  • prevunique : 游标中的数据按主键值降序排列,主键值相等只读取第一条数据

  下面让咱们来看一个完整的例子

const request = indexedDB.open('myDatabase', 4);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    const range = IDBKeyRange.bound(1,10);

    const req = store.openCursor(range, 'next');

    req.addEventListener('success', e => {
      const cursor = this.result;
        if(cursor){
            console.log(cursor.value.userName);
            cursor.continue();
        }else{
            console.log('检索结束');
        }
    })
});

复制代码

  在上面的代码中若是检索到符合条件的数据时,咱们能够:

  • 使用cursor.value拿到数据.
  • 使用cursor.updata()更新数据.
  • 使用cursor.delete()删除数据.
  • 使用cursor.continue()读取下一条数据.

索引

  在上面代码中咱们获取数据都是用的主键.可是,在不少状况下咱们并不知道咱们须要数据的主键是什么,咱们知道一个大概的条件.好比说年龄大于20岁的用户.这个时候咱们就须要用到索引.以便有条件的查找.

建立索引

  咱们使用对象仓库的createIndex()方法来建立一个索引.

createIndex(name: string, keyPath: string | string[], optionalParameters?: IDBIndexParameters): IDBIndex;
复制代码

  createIndex()方法接收三个参数:

  1. 第一个参数name是索引名,不能重复.

  2. 第二个参数keyPath是你要在存储对象上的那个属性上创建索引,能够是一个单个的key值,也能够是一个包含key值集合的数组.

  3. 第三个参数optionalParameters是一个可选的对象参数{unique, multiEntry}

    • unique: 用来指定索引值是否能够重复,为true表明不能相同,为false时表明能够相同
    • multiEntry: 当第二个参数keyPath为一个数组时.若是multiEntry是true,则会以数组中的每一个元素创建一条索引.若是是false,则以整个数组为keyPath值,添加一条索引.

  下面让咱们来看一个完整的例子,咱们创建一条用户年龄的索引.

const request = indexedDB.open('myDatabase', 5);

request.addEventListener('upgradeneeded', e => {
    const db = e.target.result;
    const  store = db.createObjectStore('Users', {keyPath: 'userId', autoIncrement: false});
    const idx = store.createIndex('ageIndex','age',{unique: false})
});
复制代码

  这样咱们就建立了一条索引.

使用索引

  这在建立了一条索引以后咱们就能够来使用它了.咱们使用对象仓库上的index方法,经过传入一个索引名.来拿到一个索引对象.

const index = store.index('ageIndex');
复制代码

  而后咱们就可使用这个索引了.好比说咱们要拿到年龄在20岁以上的数据,升序排列.

const request = indexedDB.open('myDatabase', 4);

request.addEventListener('success', e => {
    const db = e.target.result;

    const tx = db.transaction('Users','readwrite');

    const store = tx.objectStore('Users');

    const index = store.index('ageIndex');

    const req = index.openCursor(IDBKeyRange.lowerBound(20), 'next');

    req.addEventListener('success', e => {
      const cursor = e.target.result;
        if(cursor){
            console.log(cursor.value.age);
            cursor.continue();
        }else{
            console.log('检索结束');
        }
    })
});
复制代码

indexedDB的兼容性

上面是我对indexedDB一些粗浅的总结,但愿对你们有所帮助.若是文中有何不当之处请予以斧正,谢谢.

参考资料:

个人我的网址: wangyiming.info

相关文章
相关标签/搜索