IndexedDB快速入门

2019年12月12日作者:井井客来源:井井客原创

项目改版后就开始用IndexedDB去实现离线存储了(之前用的是localStorage、sessionStorage),不过我还是第一次认真去了解这块代码,其实入门挺简单的。

IndexedDB快速入门

为免阻塞应用程序,IndexedDB执行的操作是异步的,若有需要,也可引入同步API。

参考对比后端操作数据库步骤:连接数据库-(建表)-表数据增删改查操作-关闭数据库。我们前端在操作IndexedDB流程如下:
(1)打开数据库
(2)在数据库中创建一个对象仓库
(3)启动一个事务,并发送一个请求来执行一些数据库操作,像增加或提取数据等
(4)通过监听正确类型的 DOM 事件以等待操作完成
(5)在操作结果上进行一些操作

跟着这个流程,进一步了解API。

1、打开数据库

    var request = window.indexedDB.open("MyTestDatabase",'1');
    request.onerror = function (event) {
        console.log("失败后执行");
    };
    request.onsuccess = function (event) {
        console.log("成功后执行");
    };
    request.onupgradeneeded = function (event) {
        console.log("版本提升时执行");
    };

通过indexedDB.open()打开数据库,第一个参数是数据库名称,第二个参数(可选参数 - 整数)是数据库版本,默认1。若数据库不存在则创建该数据库。

indexedDB.open返回一个IDBDatabase对象的实例,打开数据库成功时,会调用该对象上的onsuccess函数,失败则调用onerror函数。

另一个特别的回调就是onupgradeneeded函数,它是数据库创建/版本需升级时触发,优先于onsuccess执行。

一个简单的使用案例,就是我们可以在onupgradeneeded中创建一个对象仓库,在onsuccess中进行该对象仓库增删改查操作,在onerror中提示错误。

onerror可能是浏览器禁用了indexedDB,也有可能是open中版本低于已有版本。

2、在数据库中创建一个对象仓库

IndexedDB存储数据是通过对象仓库,当一个值被放到对象存储空间时,它会与一个键相关联,通过键我们才能找到值。

键的值取决于对象存储空间是使用键路径还是键生成器,两者结合有4种方法。

(1)两者都不用:存储空间值支持任意类型,而键必须由我们自己提供。

(2)只用键路径:存储空间值只支持JS对象,该对象中必须有一个属性与键同名。

(3)只用键生成器:存储空间值支持任意类型,键自动生成,如需特定值也可单独提供。

(4)两者都用:存储空间值支持任意类型,该对象中必须有一个属性与键同名。与第二点不同的时,若有两个对象中该属性的值相同,则只会存在一个键,另一个数据有可能被覆盖。

键路径举例,创建一个记录学生得分的数据仓库,里面存储的值是学号、姓名和得分:

    var request = window.indexedDB.open("MyTestDatabase", "1");
    request.onerror = function (event) {
        // 失败时执行
        console.log("失败了");
    };
    request.onsuccess = function (event) {
        // 获取学生对象仓库
        var db = event.target.result;
        var studyStore = db.transaction("studys", "readwrite").objectStore("studys");
        // 假设一批学生的数据
        var studyArr = [{student_id: "10001", name: "张三", score: 100},
            {student_id: "10002", name: "李四", score: 85},
            {student_id: "10003", name: "王五", score: 92}];

        // 将数据塞到对象仓库
        studyArr.forEach(function (item) {
            studyStore.add(item);
        })
    };
    request.onupgradeneeded = function (event) {
        // 初始化创建一个学生对象仓库
        var db = event.target.result;
        db.createObjectStore("studys", {keyPath: "student_id"});
    };

效果结果如下,比较明显student_id作为键路径时,每一行Value对象中都有这个属性,而键的值与属性值一致:

键路径结果图示

来看看键生成器,这个是创建对象仓库时,通过autoIncremen标记来开启(默认不开启)。

键生成与键路径是在createObjectStore方法传参不一样:

  db.createObjectStore("studys", {autoIncrement: true});

键生成结果图示

看效果图,发现这个键生成有点累似自增主键,也好理解。

3、增加、读取和删除数据

其实在上一节获取学生对象仓库中,有一段:

var studyStore = db.transaction("studys", "readwrite").objectStore("studys");

IndexedDB需要开启一个事务,才能对数据库进行操作。db.transaction就是开启事务用的,第一个参数指定事务在哪些数据仓库上操作,可一个或多个(多个时用数组传),第二个参数(可选)指定模式:readonly、readwrite和versionchange(默认readonly),见名知义不需多说了吧。

db.transaction方法返回一个包含IDBIndex.objectStore方法的事务对象,使用 IDBIndex.objectStore 你可以访问你的对象仓库。

事务对象上,可以通过oncomplete、onerror、onabort函数接收事务上的事件回调。

        var db = event.target.result;
        var transaction = db.transaction("studys", "readwrite");
        var studyStore = transaction.objectStore("studys");
        transaction.oncomplete = function () {
            console.log("数据处理完毕");
        };
        transaction.onerror = function () {
            console.log("事务错误时执行");
        };
        transaction.onabort = function () {
            console.log("事务回滚时执行");
        };

向对象仓库中添加完数据时正常调起oncomplete,而在重复添加数据发生错误时则会调用onerror、onabort居多。

言归正传,说一下增加、读取和删除数据的API,这些其实都挺简单的。

(1)增加数据:objectStore.add()或者objectStore.put()方法。区别是add时,对象仓库中不能存在相同键对象,否则会调起transaction.onerror函数。

(2)删除数据:objectStore.delete()。

(3)获取数据:objectStore.get()。

调用这些方法后会返回一个对象,对象上通过onsuccess、onerror函数接收是否成功。而调用add、put、delete方法时,可以不多考虑,但是get时需要通过onsuccess函数接受获取到的数据。

        // 将数据塞到对象仓库
        studyArr.forEach(function (item, index) {
            studyStore.add(item);
        });
        // 获取键值为"10001"的数据
        studyStore.get("10001").onsuccess = function (event) {
            console.log("数据为:" + event.target.result)
        };

更新数据需要拿和放两个操作,通过get获取数据,然后put覆盖更新。

        // 获取键值为"10001"的数据
        studyStore.get("10001").onsuccess = function (event) {
            // 获取到数据的值
            var data = event.target.result;
            // 更新你想修改的数据
            data.score = 98;
            studyStore.put(data).onsuccess = function () {
                console.log("数据更改成功");
            }
        };

但是好像又有一个疑问点,get方法我需要指定键值(学号),尝试在不知道键值的情况下获取数据。

(1)通过openCursor()函数,解释为游标。也可以理解是指针,默认是指向第一条数据,通过onsuccess接受指向它的事件对象,调用continue则可以指向下一条数据。

        studyStore.openCursor().onsuccess = function (event) {
            var cursor = event.target.result;
            if (cursor) {
                console.log("现在指向的是学号为:" + cursor.value['student_id'] + "的数据");
                cursor.continue();
            } else {
                console.log("指针到头了");
            }
        }

这个相当于把对象仓库中的数据从第一条遍历到最后一条。

(2)通过getAll()和getAllKeys()获取所有键/值组成的数组。

        studyStore.getAll().onsuccess = function (event) {
            console.log("学生数据的集合:")
            console.log(event.target.result)
        }

另外还可以通过索引获取数据。

4、使用索引

因为上面的例子代码只是一个学生数据仓库,结构比较平面,如果数据对象中类别比较多,或者主键有重复的情况,则需要有索引来帮我们更精确的找到数据。

举例一个食物的数据仓库(后面的代码我就都不写错误处理了~):

    var request = window.indexedDB.open("MyTestDatabase", "1");
    request.onsuccess = function (event) {
        var db = event.target.result;
        var foodsStore = db.transaction("foods", "readwrite").objectStore("foods");
        // 假设一份食物数据(type代表分类,0蔬菜、1肉类)
        var foodArr = [{name: "青菜", price: 2, type: 0},
            {name: "牛肉", price: 50, type: 1},
            {name: "茼蒿", price: 5, type: 0},
            {name: "明虾", price: 30, type: 1},
            {name: "萝卜", price: 1, type: 0},
            {name: "平菇", price: 7, type: 0},
            {name: "猪肉", price: 40, type: 1}];
        // 添加数据
        foodArr.forEach(function (item) {
            foodsStore.add(item)
        });
        // 根据索引去查所有蔬菜
        var index = foodsStore.index("use type");
        index.get(1).onsuccess = function (event) {
            console.log(event.target.result)
        }
    };
    request.onupgradeneeded = function (event) {
        var db = event.target.result;
        var foodsStore = db.createObjectStore("foods", {autoIncrement: true});
        // 创建索引
        foodsStore.createIndex("use type", "type")
    };

在初始化中,就需要先过用createIndex创建索引。在添加完数据后,通过index()指定索引,并用get获取数据。

索引成结果图示

上面又有一个问题,就是index.get一次只能取一个,但是如图,实际上符合结果的数据有三条。

这时可以用getAll()获取所有数据集合。

        // 根据索引食物类别去查所有蔬菜
        var index = foodsStore.index("use type");
        index.getAll().onsuccess = function (event) {
            console.log("所有食物数据的集合:");
            console.log(event.target.result);
        };

也可用openCursor()或者openKeyCursor()逐条获取数据(同上面的游标类似)。

别外可指定游标的范围和方向:

        // 根据索引食物类别去查所有蔬菜
        var index = foodsStore.index("use type");
        var rang = IDBKeyRange.only(1);
        index.openCursor(rang, 'prev').onsuccess = function (event) {
            var cursor = event.target.result;
            if (cursor) {
                console.log(cursor.value);
                cursor.continue();
            }
        }

openCursor第一参数(可选)过滤规则,第二个参数(可选)排序,默认next。如不过滤只排序时,第一个参数传null即可。(具体规则可以参考官网,这里就不一一列举了)

基本的使用就是这些了,入门可以看看~

文章TAG:

本文标题:IndexedDB快速入门
本文链接:http://www.jingjingke.com/c/12362.html

上一篇:MAC安装低版本PHP(5.6)+MySQL(5.7.28)并运行
下一篇:Vue + 高德地图整理了几点