前端本地存储数据库存储之IndexedDB

时隔两个月,再次挥笔写文章,这篇文章,两三个月都在公司写项目自研项目,其中觉得特别浏览器的缓存我都用便了,像localStorage,sessionStorage,cookie,IndexedDB都用过遍了。我用它们来做什么呢?下面我来简单的介绍一下它们,但这篇的文章的重点是IndexedDB。

介绍

1、cookie、localStorage和sessionStorage是什么?

1)cookie
  • cookie 是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。

  • cookie 由服务器生成,发送给浏览器,浏览器把 cookie 以kv的形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该 cookie 发送给服务器。

  • cookie 的过期时间由客户端设置。若不设置过期时间,则表示这个 cookie 的生命期为浏览器会话期间,关闭浏览器窗口, cookie 就会消失。这种生命期为浏览器会话期的 cookie 被称为会话cookie。如果设置了过期时间,则在设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭也会一直有效。

  • 会话cookie一般不存储在硬盘而是保存在内存里,当然这个行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再打开浏览器后这些 cookie 仍然有效直到超过设定的过期时间。对于保存在内存里的 cookie ,不同的浏览器有不同的处理方式。

  • 可用 document.cookie = “” 来设置 cookie 的值。cookie的值是键值对的形式存在,当设置的键一样时,会覆盖掉原先的值。当键不一样时,对进行叠加操作。这里附上一篇我看过觉得比较好理解的关于如何设置cookie的文章,大家可以根据自身需求进行查看~

2)localStorage

始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;
同源窗口都会共享,并且不会失效,不管窗口或者浏览器关闭与否都会始终生效。

3)sessionStorage

浏览器存储的一种形式。
仅在当前浏览器窗口关闭前有效,不可能持久保持。
在相同浏览器里,如果是在当前页面里面跳转进入一个新的页面,可以共享;而如果是直接打开一个新的页面,不能共享。

区别

HTML5中提出了webStorage的概念,webStorage包括sessionStoragelocalStorage,只为了保存数据,不会与服务器进行通信。
cookie,localStorage,sessionStorage都是在客户端(本地)保存数据,存储数据的类型:字符串
cookie会随着HTTP请求发送到服务器,webStorage不会随着HTTP发送到服务器端,所以安全性相对来说比cookie高,不必担心截获。
webStorage拥有更大的存储量,cookie大小限制4kb,webStorage达到5M或更大
webStorage拥有setItem,getItem,removeItem,clearapicookie需要自己封装
localStorage要手动清除,sessionStorage在浏览器关闭后清除。

生命周期

cookie :可设置失效时间,否则默认为关闭浏览器后消失
localStorage :除非被手动清除,否则永久保存
sessionStorage:仅在当前网页会话下有效,关闭页面或浏览器后就会被清除

进行一个简单的对比。

特性 cookie localStorage sessionStorage indexedDB
数据生命周期 一般由服务器生成,可以设置过期时间 除非被清理,否则一直存在 页面关闭就清理 除非被清理,否则一直存在
数据存储大小 4K 5M 5M 无限
与服务端通信 每次都会携带在header中,对于请求性能有一定影响 不参与 不参与 不参与

补充cookie 原本并不是用来储存的,而是用来与服务端通信的,需要存取请自行封装 api
localStorage 则自带 getItemsetItem 方法,使用很方便。

–关于它们的介绍我就摘抄就到这,详细的介绍对于他们的看着。
参考:
cooike的看这个
三者比较详细说明看这里

那我用它们的来干嘛了。

我对它们的用途

  1. cooike,用它做了登录状态的设定,本来是用 websocetk来搞的,发现它还需要从一个门户跳转到另一个门户,这时候就可以利用它的特性来做,token的共享。
    对于cookie是区分端口的,但是浏览器实现来说,cookie区分域,而不区分端口,也就是说,同一个ip下的多个端口下的cookie是共享的!ip相同,端口不同,覆盖。
  2. 对于sessionStorage,我们有这样的一个需求,就是一个地方只能一个账号登录,而且在当前的页面有效,哪这样的需求,就可以用sessionStorage来生产存储它的临时的uuid,在这个当前标志它的唯一性,凡是当前页面用到的它的可以都携带它。
  3. localStorage用来做存取用户公共不变的信息。
  4. 对于indexDB我用来做了图片的存储。

IndexDB存储图片做动画。(分享我解决这个问题的过程)

为啥用它来做呢,本来我打算localStorage来存储的,但是我发现localStorage容量只有5M,刚好五张图,刚好超出了它的存储空间。
当然图片这种小图没必要存,大图,存起来只为了更快的响应,提升用户的体验。为啥存储这图片,因为这是五张长图,用来做首页的动画效果,跟阿里云的图标的一样,不过它的是小图,我的是大图。所以需要一个存储。
对于这个动画效果,苦恼了我两天,实现这个动画并不难,但是高性能,让它正常运作就很难,首先图片,图片我想压缩一下,发现图片已经最小了(但还是差不多有1M),在压下去分辨率就很模糊了,那不行,必须清晰,如果采用链接的形式,去请求,太久了。那也不行。也会产生了,但图片加载了一半,动画会出现鬼畜现在。那也不行。
然后对于动画,是高性能消耗,阿里的是用js去实现的,但是我不想这样玩,就动态的给它添加类名,进行一个控制。那就要onMouseLeaveonMouseEnter来控制,对于这中高性能的千万不要用react的useState,去控制某个状态,最好是原生的js。
如果说图片问题,哪采用预加载提前去加载这张图片不就行了嘛,但是就会出现一个问题,每次路由跳转回来的时候就会出现资源销毁。然后每次都发起了请求,这种几张图就几个就请求了,一般来说图片会强制缓存,采用的预加载的方式能解决了,但是后端并不想参与这件事,我的路由采用的是懒加载的方式。然后我又想到了另外一种办法,你不是让它路由跳转页面不被销毁,Vue keepalive,很不辛我用的react,那好啊,找插件,会着自己写个组件啊(自己写个是不可能的,对于摸鱼人来说),那很好,那就用插件,然后我发现会出现了一种情况,它把动画的状态都缓存了,每次路由跳转回来都会有动画效果,没有回归到初始的位置。所以这种方案又不可行。
最后采用了存取图片的方法,把图片的url的地址转为base64进行一个存储,使用到canvas,进行一个转化。然后保存到IndexDB(对于这个方法来说,base64会比原图还大一倍,但是不影响啊,它只有第一次会卡一下,让下载完在进行一个读取)。
然后效果出来了,但是每次跳出去,跳回来都会有白屏的效果,然后项目负责人说,还是不行,达美,没办法还得在想办法解决这个问题,是什么产生了这个问题呢。进行一个debug,发现是base64图片的资源太大了,react的一个影响了渲染机制需要的一个时间过渡。这个时间过渡,那这个过渡时间用什么来代替呢,然后我想到了,把长图的第一帧拿出来,只有个十几KB,这样但base64图片还没过来的时候,用这张图进行一个暂时的替代,然后当base64图片加载过来才赋予它一个动画效果。然后效果差不多了。但是还没完,进去的还没加载出来动画,在离开的时候赋予了离开的动画,那怎办,加一个判断就好了,进行一个判断是否有进去的动画的类。最后完美解决。

关键代码就几行,因为保密的原因图片跟其它的代码不能截图。

//离开的时候赋予离开的动画。并且进行一个判断是否有进去的动画。
 const handelMouseLeave = (className, entery, leave) => {
    
    
 let e = document.getElementsByClassName(className)[0]
 let temp = e.style.backgroundImage
 if(temp.includes('data:image/png')&& e.classList.contains(entery)){
    
    
   e.classList.add(leave)
   e.classList.remove(entery)
 }
}
//进行的时候赋予进去的动画。
const handelMouseEnter = (className, entery, leave) => {
    
    
 let e = document.getElementsByClassName(className)[0]
 let temp = e.style.backgroundImage
 if(temp.includes('data:image/png')){
    
    
   e.classList.add(entery)
   e.classList.remove(leave)
 }
}

关键代码很简单,都是原生的一个操作,很简单。
这是我解决怎么一个问题的思路分享给大家,有些地方可能我没考虑到。可能解决问题的过程中疏忽了一些事。把事情搞复杂了,但总算解决了。下面就进行今天的重点主题,IndexDB。

IndexedDB

MDN官网是这样解释Indexed DB的:

IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。

官网上的这句话也很简单明了,意思就是IndexedDB主要用来客户端存储大量数据而生的,我们都知道cookie、localstorage等存储方式都有存储大小限制。如果数据量很大,且都需要客户端存储时,那么就可以使用IndexedDB数据库。

对于同步还是异步MDN官网怎么说的

使用 IndexedDB 执行的操作是异步执行的,以免阻塞应用程序。IndexedDB 最初包括同步和异步 API。同步 API 仅用于 Web Workers,且已从规范中移除,因为尚不清晰是否需要。但如果 Web 开发人员有足够的需求,可以重新引入同步 API。
这种概念性的东西还是去官网了解吧,今天要分享的是对它进行的一个操作的代码的封装。

const DBVersion = 1;//版本更新
export const mainStore = 'filesImg'
打开数据库(数据库名,和版本号为1)
export function openDB(dbName) {
    
    
  //返回一个 promise对象,为了实现异步传输(创建promise对象后就会立即执行)
  return new Promise((resolve, reject) => {
    
    
    //兼容浏览器
    var indexedDB =
      window.indexedDB ||
      window.mozIndexedDB ||
      window.webkitIndexedDB ||
      window.msIndexedDB;
    let db, ObjectStore;

    //打开数据库,若没有就创建
    const request = indexedDB.open(dbName, DBVersion);

    //调用数据库打开或创建成功回调函数()
    request.onsuccess = (event) => {
    
    
      //获取 数据库对象,里面有很多方法(增删改查)
      db = event.target.result;
      // console.log('数据库打开成功!');
      //返回数据库对象
      resolve(db)
    };

    //创建或打开数据库失败回调函数
    request.onerror = (event) => {
    
    
      console.error("本地储存库打开错误");
      reject(event);
    };

    //数据库更新回调函数。初次创建,后续升级数据库用
    request.onupgradeneeded = (event) => {
    
    
      // console.log('数据库更新啦');
      //获取数据库对象
      db = event.target.result;
      // 清除旧 objectStore(对旧 objectStore 的数据进行迁移,暂未)
      if (db.objectStoreNames.contains(mainStore)) {
    
    
        // console.log(db.objectStoreNames); DOMStringList 对象
        db.deleteObjectStore(mainStore);
      }
      //创建新存储库(存储库名称,对象)-创建表(users表)
      ObjectStore = db.createObjectStore(mainStore, {
    
    
        //主键,必须唯一
        KeyPath: "fileID",
        autoIncrement: true   //实现自增加
      });

      //创建索引,在后面查询数据的时候可以根据索引查-创建字段(字段名,索引名,是否唯一:true)
      //可以通过索引来查询
      ObjectStore.createIndex('getImg', ['imgId','name']);
    }
  })
}
插入数据(数据库对象,表名,插入的数据通常为对象)
export function addData(db, storeName, data) {
    
    
  var request = db
    // 事务对象 指定表格名称和操作模式("只读""读写")
    .transaction([storeName], "readwrite")
    // 仓库对象
    .objectStore(storeName)
    .add(data);

  //成功回调函数
  request.onsuccess = function (event) {
    
    
    // console.log("数据写入成功");
  };
  //失败回调函数
  request.onerror = function (event) {
    
    
    console.error("数据写入失败");
  };
}

通过主键查询数据(数据库对象,表名,主键值)
export function getDataByKey(db, storeName, key) {
    
    
  return new Promise((resolve, reject) => {
    
    
    var transaction = db.transaction([storeName]); // 事务
    var objectStore = transaction.objectStore(storeName); // 仓库对象
    var request = objectStore.get(key); // 通过主键获取的查询数据
    //失败回调函数
    request.onerror = function (event) {
    
    
      console.log("事务失败");
    };
    //查询成功回调函数
    request.onsuccess = function (event) {
    
    
      // console.log("主键查询结果: ", request.result);
      //返回值对应的值
      resolve(request.result);
    };
  });
}
通过游标查询所有数据(数据库对象,表名)
export function cursorGetData(db, storeName) {
    
    
  let list = [];
  var store = db
    .transaction(storeName, "readwrite") // 事务
    .objectStore(storeName); // 仓库对象
  //初始化了一个游标对象(指针对象)
  var request = store.openCursor();
  //成功回调函数, 游标开启成功,逐行读数据
  request.onsuccess = function (e) {
    
    
    var cursor = e.target.result;
    if (cursor) {
    
    
      // 将查询值添加到列表中(游标.value=获取值)
      list.push(cursor.value);
      cursor.continue(); // 遍历了存储对象中的所有内容
    } else {
    
    
      // console.log("游标读取的数据:", list);
      //返回数据
      // resolve(list);
    }
  };
}
通过索引查询,查询满足相等条件的第一条数据(数据库对象,表名,索引名字,索引值)
export function getDataByIndex(db, storeName, indexName, indexValue) {
    
    
  //通过Promise实现异步回调
  return new Promise((resolve, reject) => {
    
    
    var store = db.transaction(storeName, "readwrite").objectStore(storeName);
    var request = store.index(indexName).get(indexValue);
    //查询失败回调函数
    request.onerror = function () {
    
    
      console.log("查询失败");
    };
    //查询成功回调函数
    request.onsuccess = function (e) {
    
    
      var result = e.target.result;
      // console.log(typeof (result));
      //返回成功查询数据
      resolve(result)
    }
  });
}
通过索引和游标查询多条匹配的数据(数据库对象,表名,索引名,索引值)
export function cursorGetDataByIndex(db, storeName, indexName, indexValue) {
    
    
  return new Promise((resolve, reject) => {
    
    
    let list = [];
    var store = db.transaction(storeName, "readwrite").objectStore(storeName); // 仓库对象
    var request = store
      .index(indexName) // 索引对象
      .openCursor(IDBKeyRange.only(indexValue)); // 指针对象
      
      request.onsuccess = function (e) {
    
    
      var cursor = e.target.result;
      if (cursor) {
    
    
        // 必须要检查
        list.push(cursor.value);
        cursor.continue(); // 遍历了存储对象中的所有内容
      } else {
    
    
        // console.log("游标索引查询结果:", list);
        resolve(list)
      }
    };
    request.onerror = function (e) {
    
    
      console.log('search error');
    };
  })
}

通过提供的主键值来更新数据,如果主键值不存在,就添加数据(数据库对象,表名,新的数据值)
export function updateDB(db, storeName, data) {
    
    
  return new Promise((resolve, reject) => {
    
    
    alert('err')
    var request = db
      .transaction([storeName], "readwrite") // 事务对象
      .objectStore(storeName) // 仓库对象
      .put(data);
      
    request.onsuccess = function () {
    
    
      // console.log("数据更新成功");
      resolve('数据更新成功')
    };
    request.onerror = function () {
    
    
      // console.log("数据更新失败");
      reject('数据更新失败')
    };
  })
}

通过主键删除数据(数据库对象,表名,主键值)
export function deleteDB(db, storeName, id) {
    
    
  return new Promise((resolve, reject) => {
    
    
    var request = db
      .transaction([storeName], "readwrite")
      .objectStore(storeName)
      .delete(id);
      
    request.onsuccess = function () {
    
    
      // console.log("数据删除成功");
      resolve('删除成功!')
    };
    request.onerror = function () {
    
    
      // console.log("数据删除失败");
      reject('删除失败')
    };
  })
}

通过游标和索引删除指定的多条数据(数据库对象,表名,索引名,索引值)
export function cursorDelete(db, storeName, indexName, indexValue) {
    
    
  return new Promise((resolve, reject) => {
    
    
    var store = db.transaction(storeName, "readwrite").objectStore(storeName);
    var request = store
      .index(indexName) // 索引对象
      .openCursor(IDBKeyRange.only(indexValue)); // 指针对象

    request.onsuccess = function (e) {
    
    
      var cursor = e.target.result;
      var deleteRequest;
      if (cursor) {
    
    
        deleteRequest = cursor.delete(); // 请求删除当前项
        deleteRequest.onerror = function () {
    
    
          // console.log("游标删除该记录失败");
          reject('删除失败!')
        };
        deleteRequest.onsuccess = function () {
    
    
          // console.log("游标删除该记录成功");
          resolve('删除成功!')
        };
        cursor.continue();
      }
    };
    request.onerror = function (e) {
    
     };
  })
}

关闭数据库
export function closeDB(db) {
    
    
  return new Promise((resolve, reject) => {
    
    
    db.close();
    resolve('数据库已关闭')
  })
}

IndexedDB数据库没有我们想象的那么复杂,了解了它的几个基本概念,上手还是很快的,无非就是增删改查等等,虽然可能开发中用的少,但是了解一下不至于真正用到的时候两眼抓瞎。

MDN:IndexedDB
参考:indexDB

猜你喜欢

转载自blog.csdn.net/nihaio25/article/details/129197614