IndexedDB使用与出坑指南

概述

本文经过对IndexedDB的使用方法和使用场景进行相关介绍,对常见的问题进行解答。javascript

同时,由于MDN中的相关文档缺少相关逻辑性,因此不容易理解。本文将经过项目中常见的数据存储和操做需求来进行内容组织。java

读者可以经过本文学会在项目中正确的使用IndexedDB,给应用带来的本地存储能力,而且避免一些常见的问题。git

缘由:开发者须要在本地进行永久存储

当咱们进行一些较大的SPA页面开发时,咱们会须要进行一些数据的本地存储。github

当数据量不大时,咱们能够经过SessionStorage或者LocalStorage来进行存储,可是当数据量较大,或符合必定的规范时,咱们可使用数据库来进行数据的存储。web

在浏览器提供的数据库中,共有web sqlIndexedDB两种。相较于HTML5已经废弃的web sql来讲,更推荐你们使用IndexedDBsql

结构

下面,咱们经过一张图来了解下,IndexedDB总体的结构。数据库

clipboard.png

类比sql型数据库,IndexedDB中的DB(数据库)就是sql中的DB,而Object Store(存储空间)则是数据表,Item则等于表中的一条记录segmentfault

使用IndexedDB

如今,咱们将其根据IndexedDB的结构来对其操做进行介绍,能让你们对这个存储空间有一个初步的了解。咱们主要介绍:浏览器

  • 数据库操做
  • 数据表操做
  • 数据操做

数据库操做

建立或打开数据库

使用IndexedDB第一步,就是建立或打开一个数据库。咱们使用window.indexedDB.open(DBName)这个API来打进行操做。具体示例以下:缓存

const request = window.indexedDB.open('test');

request.onupgradeneeded = function (event) {
    
}

request.onsuccess = function(event) {
    //request === event.target;
}
request.onerror = function(event) {}

调用此接口时,若是当前数据库不存在,则会建立一个新的数据库。

当数据库创建链接时,会返回一个IDBOpenDBRequest对象。

在链接创建成功时,会触发onsuccess事件,其中函数参数eventtarget属性就是request对象。

而在数据库建立或者版本更新时,会触发onupgradeneeded事件。

更新数据库版本号

window.indexedDB.open的第二个参数即为版本号。在不指定的状况下,默认版本号为1。具体示例以下:

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

在须要更新数据库的schema(模式)时,须要更新版本号。此时咱们指定一个高于以前版本的版本号,就会触发onupgradeneeded事件。相似的,当此数据库不存在时,也会触发此事件而且将版本更新到置顶版本。

咱们须要注意的是,版本号是一个Unsigned long long数字,这意味着它能够是一个很是大的整数。可是,它不能是一个小数,不然它将会被转为最近的整数,同时有可能致使onUpgradeneeded事件不触发(bug)。

存储空间操做

建立存储空间

咱们使用createObjectStore来建立一个存储空间。同时,使用createIndex来建立它的索引。具体示例以下:

var request = window.indexedDB.open('test', 1);

request.onupgradeneeded = function (event) {
    var db = event.target.result;
    var objectStore = db.createObjectStore('table1', {keyPath: 'id', autoIncrement: true});

    objectStore.createIndex('name', 'name', {unique: false});
}

request.onerror = function (event) {
    alert("Why didn't you allow my web app to use IndexedDB?!");
};

注:只能在onupgradeneeded回调函数中建立存储空间,而不能在数据库打开后的success回调函数中建立。

经过createObjectStore可以建立一个存储空间。接受两个参数:

  1. 第一个参数,存储空间的名称,即咱们上面的customers
  2. 第二个参数,指定存储的keyPath值为存储对象的某个属性,这个属性可以在获取存储空间数据的时候当作key值使用。autoIncrement指定了key值是否自增(当key值为默认的从1开始到2^53的整数时)。

createIndex可以给当前的存储空间设置一个索引。它接受三个参数:

  1. 第一个参数,索引的名称。
  2. 第二个参数,指定根据存储数据的哪个属性来构建索引。
  3. 第三个属性, options对象,其中属性unique的值为true表示不容许索引值相等。

数据操做

事务

IndexedDB中,咱们也可以使用事务来进行数据库的操做。事务有三个模式(常量已经弃用):

  • readOnly,只读。
  • readwrite,读写。
  • versionchange,数据库版本变化。

咱们建立一个事务时,须要从上面选择一种模式,若是不指定的话,则默认为只读模式。具体示例以下:

const transaction = db.transaction(['customers'], 'readwrite');

事务函数transaction的第一个参数为须要关联的存储空间,第二个可选参数为事务模式。与上面相似,事务成功时也会触发onsuccess函数,失败时触发onerror函数。

事务的操做都是原子性的。

增长数据

当存储空间初始化完成后,咱们能够把数据放入存储空间中。直接调用add方法就能够将数据放入存储空间内,具体示例以下:

var request = window.indexedDB.open('test', 1);

request.onsuccess = function (event) {
    var db = event.target.result;

    var transaction = db.transaction(['table1'], 'readwrite');

    var objectStore = transaction.objectStore('table1');

    var index = objectStore.index('name');

    objectStore.add({name: 'a', age: 10});
    objectStore.add({name: 'b', age: 20});
}

注:add方法中的第二个参数key值是指定存储空间中的keyPath值,若是data中包含keyPath值或者此值为自增值,那么能够略去此参数。

查找数据

经过特定值获取数据

当咱们须要从存储空间获取数据时,咱们能够经过如下的方法:

var request = window.indexedDB.open('test', 1);

request.onsuccess = function (event) {
    var db = event.target.result;

    var transaction = db.transaction(['table1'], 'readwrite');

    var objectStore = transaction.objectStore('table1');

    var request = objectStore.get(1);

    request.onsuccess = function (event) {
        // 对 request.result 作些操做!
        console.log(request.result);
    };

    request.onerror = function (event) {
        // 错误处理!
    };
}

经过游标获取数据

当你须要便利整个存储空间中的数据时,你就须要使用到游标。游标使用方法以下:

var request = window.indexedDB.open('test', 1);

request.onsuccess = function (event) {
    var db = event.target.result;

    var transaction = db.transaction(['table1'], 'readwrite');

    var objectStore = transaction.objectStore('table1');

    var request = objectStore.openCursor();

    request.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
            // 使用Object.assign方法是为了不控制台打印时出错
            console.log(Object.assign(cursor.value));
            cursor.continue();
        }
    };

    request.onerror = function (event) {
        // 错误处理!
    };
}

使用游标时有一个须要注意的地方,当游标便利整个存储空间可是并未找到给定条件的值时,仍然会触发onsuccess函数。

openCursoropenKeyCursor有两个参数:

  1. 第一个参数,遍历范围,指定游标的访问范围。该范围经过一个IDBKeyRange参数的方法来获取。

    遍历范围参数具体示例以下:

    // 匹配值 key === 1
    const singleKeyRange = IDBKeyRange.only(1);
    
    // 匹配值 key >= 1
    const lowerBoundKeyRange = IDBKeyRange.lowerBound(1);
    
    // 匹配值 key > 1
    const lowerBoundOpenKeyRange = IDBKeyRange.lowerBound(1, true);
    
    // 匹配值 key < 2
    const upperBoundOpenKeyRange = IDBKeyRange.upperBound(2, true);
    
    // 匹配值 key >= 1 && key < 2
    const boundKeyRange = IDBKeyRange.bound(1, 2, false, true);
    
    index.openCursor(boundKeyRange).onsuccess = function(event) {
      const cursor = event.target.result;
      if (cursor) {
        // Do something with the matches.
        cursor.continue();
      }
    };

  2. 第二个参数,便利顺序,指定游标便利时的顺序和处理相同id(keyPath属性指定字段)重复时的处理方法。改范围经过特定的字符串(IDBCursor的常量已经弃用)来获取。其中:

    • next,从前日后获取全部数据(包括重复数据)
    • prev,从后往前获取全部数据(包括重复数据)
    • nextunique,从前日后获取数据(重复数据只取第一条,索引重复即认为重复,下同)
    • prevunique,从后往前获取数据(重复数据只取第一条)

遍历顺序参数具体示例以下:

var request = window.indexedDB.open('test', 1);

request.onsuccess = function (event) {
    var db = event.target.result;

    var transaction = db.transaction(['table1'], 'readwrite');

    var objectStore = transaction.objectStore('table1');

    var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound(1, false);
    var request = objectStore.openCursor(lowerBoundOpenKeyRange, IDBCursor.PREV);

    request.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
            // 使用Object.assign方法是为了不控制台打印时出错
            console.log(Object.assign(cursor.value));
            cursor.continue();
        }
    };

    request.onerror = function (event) {
        // 错误处理!
    };
}

使用索引

在前面构建数据库时,咱们建立了两个索引。如今咱们也能够经过索引来进行数据检索。他的本质仍是经过以前获取数据的API来进行,只是将原来使用的keyPath属性转换成为了索引指定的属性。具体示例以下:

var request = window.indexedDB.open('test', 1);

request.onsuccess = function (event) {
    var db = event.target.result;

    var transaction = db.transaction(['table1'], 'readwrite');

    var objectStore = transaction.objectStore('table1');

    var index = objectStore.index('name');

    // 第一种,get方法
    index.get('a').onsuccess = function (event) {
        console.log(event.target.result);
    }

    // 第二种,普通游标方法
    index.openCursor().onsuccess = function (event) {
        console.log('openCursor:', event.target.result.value);
    }

    // 第三种,键游标方法,该方法与第二种的差异为:普通游标带有value值表示获取的数据,而键游标没有
    index.openKeyCursor().onsuccess = function (event) {
        console.log('openKeyCursor:', event.target.result);
    }
}

修改数据

当须要修改存储空间中的数据时,咱们可使用如下的API:

var objectStore = transaction.objectStore("customers");

var request = objectStore.put(data);

request.onsuccess = function (event) {
    
}

注:put方法不只可以修改现有数据,也可以往存储空间中增长新的数据。

删除数据

当咱们须要删除已经无用的数据时,咱们能够经过如下方法:

var objectStore = transaction.objectStore("customers");

var request = objectStore.delete(name);

request.onsuccess = function (event) {
    
}

异常处理

在浏览器有以下操做的状况下,indexedDB可能会出现异常:

  • 用户清除浏览器缓存
  • 存储空间超过大小限制

此时,须要对错误进行捕获,而且对用户进行提示。此章节不是本文重点,再此略过。

扩展须知

取值相关

key值可以接受的数据类型

IndexedDB中,键值对中的key值能够接受如下几种类型的值:

  • number
  • data
  • string
  • binary
  • array

具体说明能够见文档此处

key path可以接受的数据类型

当一个key值变为主键,即keyPath时,它的值就只能是如下几种:

  • Blob
  • File
  • Array
  • String

注:空格不能出如今key path中

具体说明能够见文档此处

value可以接受的数据类型

IndexedDB中,value可以接受ECMA-262中全部的类型的值,例如String,Date,ImageDate等。

事务相关

事务中断后,会不会影响key值的自增

IndexedDB在没有指定key值的时候就会采用自增的key值。若是一个事务在中途中断,那么key值的自增将会从中断的事务开始前的key开始。

安全相关

IndexedDB也受到浏览器同源策略的限制。

用户相关

清空缓存

用户在清除浏览器缓存时,可能会清除IndexedDB中相关的数据。

访问权限

部分浏览器如Safari手机版隐私模式在访问IndexedDB时,可能会出现因为没有权限而致使的异常(LocalStorage也会),须要进行异常处理。

总结

IndexedDB在本地存储中有着无可替代的做用,是替代关系型数据库web sql的产品,可以对大量数据进行存储。在许多须要运用离线存储的场景下,它可以给咱们提供有效的支撑。

可是,IndexedDB在使用过程当中仍然须要避免可能会出现的一些问题,或者对可能致使的不利影响有必定的容错处理。这样才不会对应用产生重大影响。

参考文献

相关文章
相关标签/搜索