IndexDB爬坑日志

1,031 阅读5分钟

什么是IndexDB

IndexDB是一个运行在浏览器的非关系型数据库,用来在本地存储数据,可以说是Web Storage的增强版,它不仅可以存储字符串,还可以存储二进制数据,IndexDB 理论上是没有存储上限的一般来说不会小于 250M

既然有了Web Storage那为什么要用IndexDB

从功能来看Web Storage和IndexDB都是为了在本地存储数据,那它们的相同点和不同点有哪些呢

IndexDBLocal StorageSession Storage
数据的生命周期永久保存,需要手动清除只存在于当前会话,会话结束,数据清
支持存放数据的大小一般情况下没有大小限制一般情况下PC端5M,移动端2.5M左右
易用性使用较繁琐,所有操作基于事务需要一定的数据库基础支持事件通知机制,可以将数据更新的通知发送给监听者,api的接口使用更方便,原生API比较好用,也可再次封装
操作数据方式异步同步
作用域可以在同源的不同浏览器窗口下共享数据不支持不同浏览器窗口数据共享
是否遵循同源策略
相同点都保存在浏览器端,都是同源的

IndexDB主要特点

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

  • 异步 IndexedDB 操作时不会阻塞浏览器,用户依然可以进行其他操作,而WebStorage是同步

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

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

  • 支持二进制储存IndexedDB 不仅可以储存字符串,还可以储存二进制数据

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

IndexDB中的基本概念(对象类型)

  1. IDBDatabase 对象:数据库,在每个域(协议+域名+端口号为key)下可以创建多个IDBDatabase对象(数据库),在该对象创建的同时应该创建数据库结构,版本概念:一个数据库对应一个版本号,在数据库创建完成之后要对数据库结构进行修改必须使用版本升级的方式,版本号一般来讲是一个数字,新的版本号应该大于旧的版本号
  2. IDBObjectStore 对象:对象仓库,相当于MongoDB中的collection,和关系型数据库中的表(table),每个数据库包含若干个该对象
  3. IDBIndex 对象:索引,为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引
  4. IDBTransaction 对象:事务,数据记录的读写和删改,都要通过事务完成。事务对象提供error、abort和complete三个事件监听操作结果
  5. IDBRequest 对象:操作请求,该对象是一个异步对象,使用的时候至少需要指定onsuccess,onerror两个回调函数,onsuccess回调中传回来的参数response的result属性会携带操作结果,onerror回调中传回来的参数是一个Error对象
  6. IDBKeyRange 对象:主键集合

对IndexDB的简单封装和使用(Vue项目)

IDBO.js:

/* eslint-disable no-debugger */
/* eslint-disable no-unused-vars */
export default class IDBS {
    name = null
    version = 1
    db = null
    ready = false;
    tableName = null;
    constructor(options) {
        const { databaseName, version, tableName } = options;
        this.name = databaseName;
        this.version = version;
        this.tableName = tableName;
        this.openAndUpgradeDataBase(databaseName, version, tableName)
    }
    async getDataBase() {
        const res = await this.openAndUpgradeDataBase(this.name, this.version, this.tableName);
        return res;
    }
    openAndUpgradeDataBase(databaseName, version, tableName) {
        return new Promise((resolve, reject) => {
            try {
                /*
                *@databaseName 数据仓库的名字
                *@version 数据仓库的版本
                */

                const request = window.indexedDB.open(databaseName, version);
                /*
                *数据仓库打开失败
                */
                request.onerror = (error) => {
                    console.log('IndexDB打开失败', error);
                }

                /*
                *数据仓库打开成功
                */
                request.onsuccess = (res) => {
                    const db = res.target.result;
                    console.log('IndexDB打开成功');
                    this.db = db;
                    this.ready = true;
                    resolve({ db: db });
                }

                /*
                *数据仓库升级事件(第一次新建库是也会触发,因为数据仓库从无到有算是升级了一次)
                */
                request.onupgradeneeded = (res) => {
                    const db = res.target.result;
                    this.db = db;
                    if (!db.objectthis.tableNames.contains(tableName)) {
                        let table = db.createObjectStore(tableName, { keyPath: 'id' }); // 创建数据库表
                        // table.createIndex(indexName, indexName, { unique: false });// 创建表索引
                    }
                }

            } catch (error) {
                throw new Error(error);
            }

        })


    }
    async getTable(readonly = false) {
        const { db } = await this.getDataBase();
        const transaction = db.transaction([this.tableName], readonly ? 'read' : 'readwrite'); // 事务
        const table = transaction.objectStore(this.tableName); // 仓库对象
        return table;
    }
    /**
     * add
     * @param {*} data 
     * @returns 
     */
    async addData(data) {
        const table = await this.getTable();
        const request = table.add(data);
        return this.handleIDResponse(request);
    }
    /**
     * delete
     * @param {Number} id 
     */
    async deleteDataById(id) {
        const table = await this.getTable();
        const request = table.delete(id);
        return this.handleIDResponse(request, (data, resolve) => {
            resolve(data);
            console.info('delete success:', data);
        }, (err, reject) => {
            reject(err);
        });
    }
    /**
     * update
     * @param {Object} data 
     * @returns 
     */
    async updateData(data) {
        const table = await this.getTable();
        const request = table.put(data);
        return this.handleIDResponse(request, (data, resolve) => {
            resolve(data);
            console.info('update success:', data);
        }, (err, reject) => {
            reject(err);
        });
    }
    /**
     * query
     * @param {*} key 
     * @returns 
     */
    async getDataById(id) {
        const table = await this.getTable();
        const request = table.get(id);
        return this.handleIDResponse(request, (data, resolve) => {
            resolve(data);
            console.info('query result:', data);
        }, (err, reject) => {
            reject(err);
        });
    }

    async queryList(){
        const table = await this.getTable();
        const request = table.openCursor();
        const list = [];
        return new Promise((resolve, reject) => {
            request.onsuccess = function (e) {
                let cursor = e.target.result
                if (cursor) {
                    // 必须要检查
                    list.push(cursor.value)
                    cursor.continue() // 遍历了存储对象中的所有内容
                } else {
                    resolve(list)
                }
            }
            request.onerror = function (e) {
                reject(e)
            }
        })
    }

    handleIDResponse(IDResponse, onsuccess = e => e, onerror = e => e) {
        return new Promise((resolve, reject) => {
            IDResponse.onerror = (err) => {
                onerror(err.target.error, reject);
                reject(err.target.error);
                throw new Error(err.target.error);
            };
            IDResponse.onsuccess = (e) => {
                onsuccess(IDResponse.result, resolve,e);
                resolve(IDResponse.result);
            };
        });
    }
    /**
     * 删除数据库
     * @param {String} dbName 
     * @returns 
     */
    static deleteDataBase(dbName) {
        console.log(dbName)
        let deleteRequest = window.indexedDB.deleteDatabase(dbName)
        return new Promise((resolve, reject) => {
            deleteRequest.onerror = function (err) {
                console.log('删除失败');
                reject(err);
            }
            deleteRequest.onsuccess = function (event) {
                console.log('删除成功');
                resolve(event);
            }
        })
    }
    /**
     * 关闭数据库连接
     */
    async closeDB() {
        const { db } = await this.getDataBase();
        db.close();
        console.log('数据库已关闭')
    }
}

index.js:

/* eslint-disable no-debugger */
import IDBS from "./IDBS";
const databaseName = 'indexDB';
const version = 1;
const tableName = 'person';
const db = new IDBS({ databaseName, version, tableName });
export default db;

TestIDB.vue

<template>
    <div>
        <div>
         <input type="file" @change="onFileChage">
          <input v-model="name" />
          <button @click="add">add</button>
        </div>
        <ul>
          <li v-for="v in list" :key="v.id">id:{{v.id}}----name:{{v.name}}<button @click="()=>this.delete(v.id)">delete</button><button @click="()=>this.update(v.id)">update</button></li>
        </ul>
        <div v-if="images.length>0">
          <img v-for="v in images" :key="v.id" width="200" height="100" :src="v.dataURL"/>
        </div>
    </div>
</template>

<script>
import db from './../indexDB';
export default {
  name: 'MyComponent',
  data(){
    return {
      word_pre_html:'',
      list:[],
      name:''
    }
  },
  props: {
  },
  computed:{
    images:{
      get(){
        const fileReader = new FileReader();
        fileReader.onload = ()=>{

        }
        return this.list.filter(i=>i.type==='img')
      }
    }
  },
  methods:{
  async onFileChage(e){
        const file = e.target.files[0];
        const reader = new FileReader();

        reader.onload = async(loadEvent)=> {
          const dataURL = loadEvent.target["result"];
          const res = await db.addData({id:new Date().getTime(),dataURL,type:'img'})
          console.log(loadEvent,db,res);
        };
        reader.readAsDataURL(file);

    },
    async queryList(){
          try {
            const res = await db.queryList();
            console.log(res);
            this.list = res;
            } catch (error) {
              throw new Error(error);
          }
    },
    async add(){
          try {
            const res = await db.addData({id:new Date().getTime(),name:this.name});
            console.log(res);
            this.queryList();
            } catch (error) {
              throw new Error(error);
          }
    },
    async update(id){
          try {
            const res = await db.updateData({id,name:this.name});
            console.log(res);
            this.queryList();
            } catch (error) {
              throw new Error(error);
          }
    },
    async delete(id){
          try {
            const res = await db.deleteDataById(id);
            console.log(res);
            this.queryList();
            } catch (error) {
              throw new Error(error);
          }
    }
  },
  async mounted(){
          this.queryList();
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>