玩转IndexedDB,比localStorage、cookie还要强大的网页端本地缓存


随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。

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

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

对比 cookie localStorage sessionStorage indexedDB
存储大小 4kb 5M 5M 很多于 250MB,甚至没有上限
与服务器端通讯 每次都会携带在HTTP头中,若是使用cookie保存过多数据会带来性能问题 仅在客户端(即浏览器)中保存,不参与和服务器的通讯
生命周期 通常由服务器生成,可设置失效时间。若是在浏览器端生成Cookie,默认是关闭浏览器后失效 除非被清除,不然永久保存 仅在当前会话下有效,关闭页面或浏览器后被清除 除非被清除,不然永久保存
使用场景 判断用户是否登陆 存储一些内容稳定的资源。好比图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串 存储一些当前会话的信息,好比微博的 sessionStorage就主要是存储你本次会话的浏览足迹 和 localStorage 用途相似:
1.  存储量会更大
2. localStorage使用简单字符串键值对在本地存储数据,而indexedDB能够存储任意类型的值(适合键值对较多的数据,若是使用 localStorage 存储每次都要写入,写出须要字符串化和对象化)
复制代码


目前,Chrome 27+、Firefox 21+、Opera 15+和IE 10+支持这个API,但是Safari完全不支持。

下面的代码用来检查浏览器是否支持这个API。

if("indexedDB" in window) {
    // 支持
} else {
    // 不支持
}

IndexedDB 具有以下特点。

(1)键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。

(2)异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

扫描二维码关注公众号,回复: 16330319 查看本文章

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

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

(5)储存空间大。 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。

(6)支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

IndexedDB 是一个比较复杂的 API,涉及不少概念。它把不同的实体,抽象成一个个对象接口。学习这个 API,就是学习它的各种对象接口。

  • 数据库:IDBDatabase 对象
  • 对象仓库:IDBObjectStore 对象
  • 索引: IDBIndex 对象
  • 事务: IDBTransaction 对象
  • 操作请求:IDBRequest 对象
  • 指针: IDBCursor 对象
  • 主键集合:IDBKeyRange 对象

下面是一些主要的概念。

(1)数据库

数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。

IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。

(2)对象仓库

每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。

(3)数据记录

对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。

{ id: 1, value: '对应的值' }

上面的对象中,id属性可以当作主键。

数据体可以是任意数据类型,不限于对象。

(4)索引

为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。

(5)事务

数据记录的读写和删改,都要通过事务完成。事务对象提供errorabortcomplete三个事件,用来监听操作结果。


用例代码 

<template>
    <div>
        <el-input v-model.trim="databaseName" :placeholder="`请输入数据库名称`" />
        <el-input v-model.trim="tableName" :placeholder="`请输入表名称`" />
        <el-input v-model.trim="index" :placeholder="`请输入字段名`" />
        <el-button type="primary" @click="btn1()">创建数据库</el-button>

        <hr>

        <el-input v-model.trim="value" :placeholder="`请输入值`" />
        <el-button type="success" @click="btn2()">添加数据</el-button>

        <hr>

        <el-input v-model.trim="keyPathValue" :placeholder="`请输入主键值`" />
        <el-input v-model.trim="newValue" :placeholder="`请输入修改值`" />
        <el-button type="warning" @click="btn3()">修改数据</el-button>

        <hr>

        <el-button type="info" @click="btn4()">读取数据</el-button>
        <template v-if="tableData.length">
            <el-button type="danger" @click="btn5">删除全部</el-button>
            <hr>
            <el-button type="info" @click="btn6(keyPathValue)">读取某一条数据</el-button>
            <span>{
   
   { oneData ? JSON.stringify(oneData) : '没有获取到对应数据' }}</span>
            <el-table :data="tableData">
                <el-table-column :prop="keyPath" :label="keyPath" />
                <el-table-column :prop="index" :label="index" />
                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button size="mini" type="danger" @click.stop="btn7(scope.row[keyPath])">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
        </template>
    </div>
</template>
<script>
export default {
    data() {
        return {
            databaseName: '数据库',
            tableName: '表',
            keyPath: 'id',
            index: '字段',
            keyPathValue: '',
            value: '',
            newValue: '',
            tableData: [],
            oneData: {},
        }
    },
    methods: {
        // 用例----------------------------------------
        // 创建
        btn1() {
            let databaseName = this.databaseName;
            let version = this.version;
            let tableName = this.tableName;
            let keyPath = this.keyPath;
            let indexs = [
                [this.index, this.index, { unique: false }],
            ];//如需定义多格字段,就多几个数组
            // 创建表
            this.creatDatabaseTable({
                databaseName, version,
                tableName,//定义表名
                keyPath,//定义主键
                indexs,// 定义索引字段
            });
        },
        // 添加
        btn2() {
            let databaseName = this.databaseName;
            let version = this.version;
            let tableName = this.tableName;
            let keyPath = this.keyPath;
            let index = this.index;
            this.addData({
                databaseName, version, tableName,
                data: {
                    [keyPath]: '********'.replace(/\*/g, () => Math.round(Math.random() * 15).toString(16)),//随机id
                    [index]: this.value,
                },
                onsuccess: d => { console.log(`onsuccess`, d); },
                onerror: d => { console.log(`onerror`, d); },
            })
        },
        // 修改
        btn3() {
            let databaseName = this.databaseName;
            let version = this.version;
            let tableName = this.tableName;
            let keyPath = this.keyPath;
            let index = this.index;
            this.updateData({
                databaseName, version, tableName,
                data: {
                    [keyPath]: this.keyPathValue,
                    [index]: this.newValue,
                }
            });
            this.btn4();//刷新数据
        },
        // 读取
        btn4() {
            let databaseName = this.databaseName;
            let version = this.version;
            let tableName = this.tableName;
            this.readAllData({
                databaseName, version, tableName,
                onsuccess: ({ data }) => { this.tableData = data },
            })
        },
        // 删除全部
        btn5() {
            let databaseName = this.databaseName;
            let version = this.version;
            let tableName = this.tableName;
            this.delAllData({ databaseName, version, tableName, });
            this.btn4();//刷新数据
        },
        // 读取某一条数据
        btn6(id) {
            let databaseName = this.databaseName;
            let version = this.version;
            let tableName = this.tableName;
            let keyPath = this.keyPath;
            this.readData({
                databaseName, version, tableName,
                data: { [keyPath]: id },//需要读取的数据主键
                onsuccess: ({ data }) => {
                    this.oneData = data;
                },
            })
        },
        // 删除某一条数据
        btn7(id) {
            let databaseName = this.databaseName;
            let version = this.version;
            let tableName = this.tableName;
            let keyPath = this.keyPath;
            this.delData({
                databaseName, version, tableName,
                data: { [keyPath]: id },//需要删除的数据主键
                onsuccess: d => {
                    this.btn4();//刷新数据
                },
                onerror: d => { console.log(`onerror`, d); },
            })
        },
        // indexedDB_________________________________________________________
        // 1、创建or打开客户端数据库
        createDatabase({ databaseName, version = 1, onupgradeneeded, onsuccess, onerror } = {}) {
            let request = window.indexedDB.open(databaseName, version);
            request.onupgradeneeded = onupgradeneeded;
            request.onsuccess = onsuccess;
            request.onerror = onerror;
        },
        getDatabase(obj) { return this.createDatabase(obj); },//获取数据库
        // 2、创建表
        creatDatabaseTable({ databaseName, version, tableName, keyPath, indexs, onupgradeneeded, onsuccess, onerror } = {}) {
            this.getDatabase({
                databaseName, version,
                onupgradeneeded: d => {
                    let database = d.target.result;
                    if (!database.objectStoreNames.contains(tableName)) {
                        //createObjectStore只能在onupgradeneeded里面执行
                        let objectStore = database.createObjectStore(tableName, { keyPath });
                        (indexs || []).forEach(v => objectStore.createIndex(...v));
                        onupgradeneeded && onupgradeneeded({ event: d, objectStore });
                    }
                },
                onsuccess,
                onerror,
            })
        },
        // 3、添加数据or修改数据or删除数据
        addData({ databaseName, version, tableName, data, onsuccess, onerror, triggerName = 'add' } = {}) {
            this.getDatabase({
                databaseName, version,
                onsuccess: d => {
                    let database = d.target.result;
                    if (database.objectStoreNames.contains(tableName)) {
                        let objectStore = database.transaction(tableName, 'readwrite').objectStore(tableName);
                        let request_objectStore = objectStore.get(data[objectStore.keyPath]);
                        request_objectStore.onsuccess = event => {
                            if (triggerName === 'delete') {
                                data = data[objectStore.keyPath];
                            } else {
                                event.target.result && (triggerName = 'put');//如果已经存在该主键数据,就变成修改
                            }
                            let request = objectStore[triggerName](data);
                            request.onsuccess = onsuccess;
                            request.onerror = onerror;
                        };
                    } else onsuccess && onsuccess({ msg: '表格不存在!', data: [] });
                },
            })
        },
        // 4、修改数据
        updateData(obj) { this.addData({ ...obj, triggerName: 'put' }) },
        // 5、删除数据
        delData(obj) { this.addData({ ...obj, triggerName: 'delete' }) },
        delAllData({ databaseName, version, tableName } = {}) {
            this.getDatabase({
                databaseName, version,
                onsuccess: d => {
                    let database = d.target.result;
                    if (database.objectStoreNames.contains(tableName)) {
                        let objectStore = d.target.result.transaction(tableName, 'readwrite').objectStore(tableName);
                        objectStore.clear();
                    } else onsuccess && onsuccess({ msg: '表格不存在!', data: [] });
                },
            })
        },
        // 6、读取数据
        readAllData({ databaseName, version, tableName, onsuccess } = {}) {
            this.getDatabase({
                databaseName, version,
                onsuccess: d => {
                    let database = d.target.result;
                    if (database.objectStoreNames.contains(tableName)) {
                        let objectStore = database.transaction(tableName).objectStore(tableName);
                        let data = [];
                        objectStore.openCursor().onsuccess = event => {
                            let cursor = event.target.result;
                            if (cursor) {
                                data.push(cursor.value); cursor.continue();
                            } else {
                                onsuccess && onsuccess({ event, data });
                                // console.log('没有更多数据了!'); 
                            }
                        };
                    } else onsuccess && onsuccess({ msg: '表格不存在!', data: [] });
                },
            })
        },
        // 读取某一条数据
        readData({ databaseName, version, data, tableName, onsuccess } = {}) {
            this.getDatabase({
                databaseName, version,
                onsuccess: d => {
                    let database = d.target.result;
                    if (database.objectStoreNames.contains(tableName)) {
                        let objectStore = database.transaction(tableName).objectStore(tableName);
                        let request_objectStore = objectStore.get(data[objectStore.keyPath]);
                        request_objectStore.onsuccess = event => {
                            let data = event.target.result;
                            onsuccess && onsuccess({ event, data });
                        };
                    } else onsuccess && onsuccess({ msg: '表格不存在!', data: [] });
                },
            })
        },
        // 判断是否存在某个表        
        hasDatabaseTable({ databaseName, version, tableName, onsuccess } = {}) {
            this.getDatabase({
                databaseName, version,
                onsuccess: event => {
                    let database = event.target.result;
                    let data = database.objectStoreNames.contains(tableName)
                    onsuccess && onsuccess({ msg: data ? '表格存在!' : '表格不存在!', event, data });
                },
            })
        },
        // 删除某个数据库
        delDatabase(
            { databaseName, version = 1, onsuccess, onerror } = {}) {
            let request = window.indexedDB.deleteDatabase(databaseName, version);
            request.onsuccess = onsuccess;
            request.onerror = onerror;
        },
        // ____________________________________________________________________________
    }
};
</script>

猜你喜欢

转载自blog.csdn.net/qq_37860634/article/details/132283985