浅析IndexedDB前端存储方案

2,057 阅读8分钟

一、为什么要使用IndexedDB

随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量的数据存储在客户端,因为能减少从服务器获取数据,可以直接从本地获取数据,但是现有几种浏览器数据存储方案都不适合存储大量数据,而Indexeded就可以大量存储数据,下面就先讲讲现有的几种存储方案以及他们区别。

二:浏览器端存储方案介绍

1.1 方案介绍

1.1.1 Cookie

就是小甜饼的意思,所以它容量非常的小,大小限制为4K左右,是网景公司前雇员Lou Montulli在1993年3月的发明,它主要是保存登录信息,比如你登录某个网站时通常可以看到“记住密码”,这就是通过在cookie中存入一段辨别用户身份的数据来实现的

1.1.2 Web Storage

Web Storage 包括localSortage、sessionStorage

1、 localSortage:是HTML5标准中新加入的技术,并不是划时代的新东西,在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持。

2、sessionStorage: 只存在于当前tab页面,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空

1.1.3 IndexedDB

就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。
这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

1.2 方案区别

difirent.png

三:IndexedDB应用场景

1.1 应用场合

1、需要存储大量结构化数据得项目

2、有些需要在线编辑的项目

1.2 实际项目应用

1、北京优锘科技旗下的ThingMap上的地图的瓦片、建筑、河流、各种图层等等都是通过indexedDB存储的(thingmap.thingjs.com/theme)

2、谷歌、百度地图相关的一些3D对象数据

3、京东、腾讯课堂有些数据同样存的indexedDB

4、ThingJS - 物联网3D可视化PaaS平台 - 数字孪生可视化平台 里面的在线开发代码也是存的indexedDB(www.thingjs.com/guide/)

四:IndexedDB特性

1.1 可存储的内容

IndexedDB内部采用对象仓库(object store)存放数据。什么意思呢,就是可以存放所有类型的数据(字符串、数字、对象、数组、Date、文件、甚至还可以存储二进制数据如:ArrayBuffer 对象和 Blob 对象),包括JS对象,直接存储对象不需要像localStorage做序列化操作,对象仓库中,数据以“键值对”的方式保存,每一条数据记录都有主键,是独一无二的,重复会抛出错误

1.2 异步

整个操作是异步的,localStorage是同步的,获取数据会阻塞程序的运行,而IndexedDB获取数据会类似于一个Promise这样一种异步的操作,异步设计是为了防止大量数据的读写,拖慢网页。

1.3 支持事务

IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。

1.4 同源限制

IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,不能访问跨域的数据库。

1.5 存储空间大
IndexedDB 的储存空间比 LocalStorage 大得多,一般可达到500M,甚至没有上限。 area.png

上面那张图说的就是不同的前端存储方案的容量对比,indexedDB可以达到五百兆,不同的浏览器存储容量会有所不同,有的项目在浏览器里面可能会达到1G,但是这时候会比较卡,这也算是缺点之一,所以不是所有的场景都使用IndexedDB。

五:IndexedDB操作步骤

IndexedDB 数据库的各种操作,一般是按照下面的流程进行的。这个部分只给出简单的代码示例,用于快速上手,详细的各个对象的 API 请看这里(wangdoc.com/javascript/…

在线 代码编辑地址:codepen.io/qswzch/pen/…

1.1 打开数据库

code1.png

1.2 新建数据库

新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。

code2.png

createObjectStore方法存放数据的对象仓库,类似于传统关系型数据库的表格,返回一个 IDBObjectStore 对象。该方法只能在versionchange事件监听函数中调用。

1.3 新增数据

新增数据指的是向对象仓库写入数据记录,这需要通过事务完成。

code3.png

transaction()方法会返回IDBTransaction 对象用来异步操作数据库事务,所有的读写操作都要通过这个对象进行
写入操作是一个异步操作,通过监听连接对象的success事件和error事件,了解是否写入成功。

1.4 读取数据

读取数据也是通过事务完成

code4.png

1.5 遍历数据

遍历数据表格的所有记录,要使用指针对象 IDBCursor。

code5.png

1.6 更新数据

更新数据要使用IDBObject.put()方法。

code6.png

1.7 删除数据

IDBObjectStore.delete()方法用于删除记录。

code7.png

1.8 使用索引

索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。

上面得代码中分别对name和email建立了索引

code8.png

1.9 完整代码

/**
 * 打开数据库
 * open这个方法接受两个参数
 * 第一是数据库得名字,如果指定的数据库不存在就会新建
 * 第二个是数据库版本号,如果不传,打开已有数据库时,
 * 默认为当前版本,新建数据库时默认为1
*/
const request = indexedDB.open('dataBase1',1)
let db; //用于存储数据库

// open() 方法返回一个IDBRequest对象,这个对象会通过三种事件处理打开数据库的操作结果

// error事件表示打开数据库失败
request.onerror = (event) => {
console.log('数据库打开报错');
}
// success事件表示成功打开数据库。
request.onsuccess  = (event) => {
    // 通过request.result属性拿到数据库对象
    db = request.result;
    console.log(db);
    console.log('数据库打开成功');
    add()
    read()
    update()
    remove()
    readAll()
    indexUsing()
}
// upgradeneeded事件表示如果指定得版本号大于数据库的实际版本号,会发生数据库升级事件
// 新增数据库后续操作
request.onupgradeneeded = (event) =>  {
    // 如果数据库不存在就新建,新建的后续操作在该方法中完成
    db = event.target.result; // 拿到数据库实例
    let objectStore
    if (!db.objectStoreNames.contains('person')) {
        // 新增一个person表, 主键是id, 主键是默认建立索引的属性
        // 主键也可以指定为下一层对象的属性,比如{ foo: { bar: 'baz' } }的foo.bar也可以指定为主键。
        objectStore = db.createObjectStore('person',{ keyPath: 'id' })

        // 如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键
        // objectStore = db.createObjectStore('person',{ autoIncrement: true })

        // 新建索引,createIndex三个参数是索引名称、索引所在属性、配置对象(表明该属性是否包含重复的值)
        objectStore.createIndex('name','name',{unique: false})
        objectStore.createIndex('eamil','eamil',{unique: true})
    }
}

// 新增数据
function add(){
    // 新建一个事务,指定表格名称和操作模式,操作模式分为读写和只读
    // 事务创建完成会通过IDBTransaction.objectStore(name)方法拿到IDBObjectStore 表格对象
    // 通过表格对象的add方法向表格写入记录
    const request = db.transaction(['person'],'readwrite').objectStore('person').add({
        id: 3, name: '王五', age: 27, email: 'wangwu@example.com' 
    })

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

    request.onerror  = (event) => {
        console.log(event)
        console.log('数据写入失败');
    }
}

// 读取数据
function read() {
    const transaction = db.transaction(['person'])
    const objectStore = transaction.objectStore('person')
    // 用于读取数据,参数是主键的值
    const request = objectStore.get(3)

    // 失败
    request.onerror = (event) =>  {
        console.log('事务失败');
    };

    // 成功
    request.onsuccess = (event) => {
        const rslt = request.result
        if (rslt) {
            console.log('name:' + rslt.name)
            console.log('age:' + rslt.age)
            console.log('email:' + rslt.email)
        }else {
            console.log('未获得数据记录');
        }
    }

}

// 遍历数据
function readAll() {
    const transaction = db.transaction(['person'])
    const objectStore = transaction.objectStore('person')

     // IDBCursor 对象代表指针对象,用来遍历数据仓库(IDBObjectStore)或索引(IDBIndex)的记录
     // IDBCursor对象是个异步对象,需要监听success
    objectStore.openCursor().onsuccess = (event) => {
        const 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('没有更多数据了!');
        }
    }
}

// 更新数据
function update(){
    const transaction = db.transaction(['person'],'readwrite')
    const objectStore = transaction.objectStore('person')

    // 使用put方法,自动更新主键为1得记录
    const request = objectStore.put({
        id: 1,
        name: 'lisan',
        age: 35,
        email: 'lisan@example.com'
    })

    request.onsuccess =  (event) => {
        console.log('数据更新成功');
    };

    request.onerror = (event) => {
        console.log('数据更新失败');
    }
}

// 删除数据
function remove () {
    const transaction = db.transaction(['person'],'readwrite')
    const objectStore = transaction.objectStore('person')
    const request = objectStore.delete(1)
    request.onsuccess = (event) => {
        console.log('数据删除成功')
    }
}

// 使用索引
function indexUsing(){
    const transaction = db.transaction(['person'],'readonly')
    const store = transaction.objectStore('person')
    // index()方法返回指定名称的索引对象 IDBIndex
    const index = store.index('name')
    const request = index.get('王五')

    request.onsuccess = (event) => {
        const rslt = event.target.result
        if(rslt){
            console.log(rslt)
            console.log('Id:' + rslt.id)
            console.log('Name:' + rslt.name)
            console.log('Age:' + rslt.age)
            console.log('Email:' + rslt.email)
        }else {
            console.log('没有查到!');
        }
    }
}