Web前端基础篇-HTML5-05-最全本地存储总结

本地存储对于一个前端来说是一项必不可少的技能,虽然你一直在使用,但是你不一定全部了解,那么现在又开始老夫的装B时刻-show time


tmpdir__17_9_7_15_04_07.jpg

温馨提示:文章结构如下,阅读完可能需要花费5分钟

一、 cookie : 为了浏览器兼容性 H5之前很长一段时间用, 但是现在不怎么用啦
二、 storage: (包含 localstorage/ sessionstorage)本地存储
三、WebSQL: 网页版的数据库 - 很少使用,但是还是讲讲
四、 indexDB: 本地缓存数据库 - 可以替代WebSQL

进入正文

一、 cookie
  1. cookie的优点:

    A.基本所有浏览器都适用
    B.同源http 中携带 -只要有请求涉及cookie,cookie就要在服务器和浏览器之间来回传送

  2. cookie的缺点

A.cookie空间大小:4K,不适合存业务数据
B .cookie安全性问题:由于在HTTP请求中的cookie是明文传递的(HTTPS不是),带来的安全性问题还是很大的
C.网络负担:我们知道cookie会被附加在每个HTTP请求中,请求和返回都会有,浪费流量

  1. cookie 常用的API

    // 设置cookie
    function setCookie(c_name, value, expireDay, path, domain, secure = true) {
    let cookieName = encodeURIComponent(c_name) + '=' + encodeURIComponent(value);
    if (expireDay) {
    const exdate=new Date()
    exdate.setDate(exdate.getDate() + expiredays);
    cookieName += ';expires=' + exdate.toUTCString()
    }
    if (path) {
    cookieName += ';path' + path
    }
    if (domain) {
    cookieName += ';domain=' + domain
    }
    if (secure) {
    cookieName += ';secure=' + secure
    }
    document.cookie = cookieName;
    }
    /**

    • 获取cookie
      /
      function getCookie(c_name){
      if(document.cookie.length > 0){
      const cookieName = encodeURIComponent(c_name) + '=';
      const startIndex = document.cookie.indexof(cookiename);
      let cookieValue = ''
      if(startIndex !== -1){
      let endIndex = document.cookie.indexof(';', startIndex);
      if(endIndex === -1) {
      endIndex = document.cookie.length;
      }
      //获取对应cookieName的value值
      cookieValue = document.cookie.substring(startIndex + cookieName.length, endIndex)
      }
      }
      }
      /
    • 移除cookie
      */
      function deleteCookie(c_name, value) {
      if(document.cookie.length > 0){
      const cookieName = encodeURIComponent(c_name) + '=' + encodeURIComponent(value);
      const exp = new Date(0);
      document.cookie = cookieName + ';expires=' + exp.toUTCString();
      }
      }
  2. 参数说明
    name: 名称
    value: 值
    path:路径
    expires:过期时间,如果不设置过期时间,关闭浏览器消失,如果设置过期时间存到硬盘中,默认存在内存中
    secure: 安全
    domain:web 共享cookie

  3. 适用场景

cookie 空间小,但是可以利用http来回传输的优点可以保存用户id 或者token验证信息,但是要注意加密

注意:一般情况我们很少去封装cookie, 因为有插件js-cookies
二. storage - 本地存储我们最常用

1.分类:
A-localStorage: 本地永久性存储数据,除非手动将其删除或清空
B-sessionStorage:存储的数据只在会话期间有效(会话存储、临时存存储),关闭浏览器则自动删除

2.存储方式 : key- value 键值对方式

3.存储大小 : 每个域名5M

4.存储内容:数组,图片,json,样式等(只要是能序列化成字符串的内容都可以存储)都需要序列化为字符串类型

5.浏览器兼容情况: IE8.0+ Chrome 4.0+ FireFox 3.0+ 等

6.常用的API:

  • length:唯一的属性,只读,用来获取storage内的键值对数量;
  • key:根据index获取storage的键名
  • getItem:根据key获取storage内的对应value
  • setItem:为storage内添加键值对
  • removeItem:根据键名,删除键值对
  • clear:清空storage对象

7.事件StorageEvent -监听storage 变化

image.png
  1. 使用场景

保存少量业务数据,比如用户信息,但是需要加密

  1. 怎么设计一个storage 工具类
    A.、因为空间有限,应可配置数据失效时间
    B、 需要处理内存溢出
    C、最好有加密
    下面是老夫参考别人设计的一个简单类(暂时没有考虑加密,不想看的可以直接略过)
 /**
* 本地保存localstorage
*/
import ZBConsole from "../common/ZBConsole.js";
const LocalStorageHelper = {
/**
 * 保存数据
 * @param key  保存的key
 * @param value   保存的值
 * @param expires  过期时间
 */
setValue(key, value, expires){
   const _d = new Date();
   // 存入日期
    const inTime =_d.getTime();

    if(!expires){
        _d.setTime(inTime + this.defaultExpeierTime);
        expires = _d.getTime();
    }
    if(!this.__isSupportValueType(value)){
        return false;
    }


    // 保存所有的key 数组
    this.__setKeyCache(key, expires);

    // 需要保存的对象
    const entity = this.__createStorageObj(value,inTime,expires);
    const entityStr = JSON.stringify(entity);

    // 保存对象
    try{
        this.storeProxy.setItem(key, entityStr);
        return true;
    }catch (e) {
        // localstorage内存溢出
        if(e.name === 'QuotaExceededError'){
            // 溢出过期时间最长的数据
            if(!this.__removeLastCache()){
                this.__asset('溢出移除数据失败');
            }
            // 重新保存
            this.setValue(key,value,expires);

        }
    }
    return false;

},
/**
 * 获取数据
 */
getValue(key){
    const nowTime = new Date().getTime();
    let resultObject = null;
    try {
        const resultObjectStr = this.storeProxy.getItem(key);
        if(!resultObjectStr || resultObjectStr.length == 0){
            return null;
        }
        resultObject = JSON.parse(resultObjectStr);
        //数据过期
        if (resultObject.expires < nowTime){
            return null;
        }
        // 数据正常
        const valueStr = resultObject.value;
        if(valueStr === this.dataTypes.type_Array
            || valueStr === this.dataTypes.type_Object){
            const dataList = JSON.stringify(valueStr);
            return dataList;
        }else {
            return valueStr;
        }

    }catch (e) {
        console.log('获取数据失败--',e);
    }
},


/**
 * 溢出过期时间最近的的数据
 */
__removeLastCache(){

    // 移除次数
    const num = this.removeNum || 5;
    //取出键值对
    const cacheStr = this.storeProxy.getItem(this.keyCache);

    // 说明没有数据需要移除
   if (!cacheStr || cacheStr.length === 0){
       return false;
   }

   const cacheKeyMap = JSON.parse(cacheStr);
   if(!_.isArray(cacheKeyMap)){
       return false;
   }

   // 时间排序
    cacheKeyMap.sort(function (a,b) {
        return a.expires - b.expires;
    });

    // 删除数据
    const delKeyMap = cacheKeyMap.splice(0, num);

    for (let i = 0; i < delKeyMap.length; i++) {
         const item = delKeyMap[I];
         this.storeProxy.removeItem(item.key);
    }

    // 保存key
    this.storeProxy.setItem(this.keyCache, JSON.stringify(cacheKeyMap));

    return true;
},


/**
 * 移除过期的数据
 */
removeExpiresCache(){
    //1. 取出当前的所有key
    const cacheKeyStr = this.storeProxy.getItem(this.keyCache);
    if(!cacheKeyStr || cacheKeyStr.length === 0){
        return false;
    }
    const cacheKeyMap = JSON.parse(cacheKeyStr);
    if(!_.isArray(cacheKeyMap)){
        return false;
    }

    // 2.获取当前时间
    const nowTime = new Date().getTime();

    // 3.比较过期时间
    const newKeyMap = [];
    for (let i = 0; i < cacheKeyMap.length; i++) {
        const item = cacheKeyMap[I];
        if(item.expires < nowTime){
            // 已经过期
            this.storeProxy.removeItem(item.key);
        }else {
            // 没有过期
            newKeyMap.push(item);
        }
    }

    const jsonStr = JSON.stringify(newKeyMap);
    this.storeProxy.setItem(this.keyCache, jsonStr);

    return true;

},

/**
 * 保存key到内存中
 */
__setKeyCache(key, expires){
    if (!key || !expires || expires < new Date().getTime()){
        return false;
    }
   const  saveStr = this.storeProxy.getItem(this.keyCache);

   const obj = {};
   obj.key = key;
   obj.expires = expires;

     // 1.之前保存的数据为null
    let saveList = [];
    if(!saveStr || saveStr.length == 0){
       saveList.push(obj);
       return true;
    }

    // 2. 之前的保存的数据有值但是不是数组
    saveList = JSON.parse(saveStr);
    if(!_.isArray(saveList)){
        saveList = [];
        saveList.push(obj);
        return true;
    }

    // 3. 判断key值是否是之前保存的
    let findKey = false;
    for (let i = 0; i < saveList.length; i++) {
        let item = saveList[I];
        if(item.key === key){
            item = obj;
            findKey = true;
            break;
        }
    }
    if(!findKey){
        saveList.push(obj);
    }

    // 4. 保存所有的key
    const jsonStr =  JSON.stringify(saveList);
    this.storeProxy.setItem(this.keyCache, jsonStr);
},

/**
 * 创建保存的object
 */
__createStorageObj(vaule, inTime, expires){
    if(!vaule){
        this.__asset('保存值不能为null');
        return;
    }
    let saveValue = vaule;
    if(vaule instanceof Object){
        saveValue = JSON.stringify(vaule);
    }
    const obj = {
        value: saveValue,
        inTime: inTime,
        expires: expires,
        type: Object.prototype.toString.call(vaule),
    }
    return obj;
},

/**
 * 是否支持这个类型是值
 */
__isSupportValueType(value){
   if(!value){
       this.__asset('保存的value不能为null');
       return false;
   }
   const objectStr = Object.prototype.toString.call(value);
   if(objectStr === "[object Symbol]"){
       this.__asset('保存的value不支持Symbol类型');
       return false;
   }

   return true;
},

/**
 * 初始化
 */
__initialize(){
    this.__propertys();
},
/**
 * 设置默认属性
 */
__propertys(){
    //代理对象,默认为localstorage
    this.storeProxy = window.localStorage;
    if(!this.storeProxy){
        this.__asset('浏览器不支持localstorage');
        return false;
    }

    //60 * 60 * 24 * 30 * 1000 ms ==30天 过期时间
    this.defaultExpeierTime = 2592000000;

    //本地缓存用以存放所有localstorage键值与过期日期的映射
    this.keyCache = 'SYSTEM_KEY_TIMEOUT_MAP';

    //当缓存容量已满,每次删除的缓存数
    this.removeNum = 5;

    // 数据类型
    this.dataTypes = {
        type_Array: "[object Array]",
        type_Object: "[object Object]",
        type_Boolean: "[object Boolean]",
        type_String: "[object String]",
        type_Number: "[object Number]",
        type_Symbol : "[object Symbol]",
    }

},

/**
 * 断言
 */
__asset(message){
    ZBConsole.logTrace(message);
    throw message;
 }
};
;(function(window){
// 暴露类
function ZBStorage() {
    LocalStorageHelper.__initialize();
    // 暴露方法
    ZBStorage.prototype.setValue = function(key, value, expires){
        LocalStorageHelper.setValue(key, value, expires)
   }

    ZBStorage.prototype.getValue = function (key) {
       return LocalStorageHelper.getValue(key)
   }

}
  window.ZBStorage = new ZBStorage();
})(window);
 export default  LocalStorageHelper;

结果:


三. WebSQL - 可以在最新版的 Safari, Chrome 和 Opera 浏览器中使用,但是很少用

相对有过数据库经验的人很简单,基本就是执行SQL语句

  1. 支持情况
    Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作

  2. 核心API
    openDatabase:新建的数据库创建一个数据库对象或者打开已有的数据库
    transaction: 执行SQL语句的事务
    executeSql: 执行SQL语句

3.存储大小: 自定义设置大小

  1. 使用场景:对数据库设计比较了解的人,存储大量数据

  2. 怎么设计存储工具类

 /**
 * web sql Web SQL 数据库可以在最新版的 Safari, Chrome 和   Opera 浏览器中工作
 */
export default {
/**
 * 打开数据库
 */
openSQLite(){
    if(!this.sqlDB){
        //  数据库名称、版本号、描述文本、数据库大小、创建回调
        this.sqlDB = openDatabase('zbsqlite','1.0.0','数据库', 2*1024*1024, function () {

        });
    }
},
/**
 * 执行sql 语句
 */
executeSql(sql){
    this.openSQLite();
    if(!sql || sql.length == 0){
        return;
    }
    this.sqlDB.transaction(function (tx) {
        tx.executeSql(sql);
    });
},
/**
 * 创建表
 */
createTable(tableName){
   const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (id unique, name, age)`;
   this.executeSql(sql);
},
/**
 * 插入数据
 */
insetTable(){
    const sql_data01 = 'INSERT INTO zbtestTable (id, name, age) VALUES (1, "甄姬", 15)';
    const sql_data02 ='INSERT INTO zbtestTable (id, name, age) VALUES (2, "安其拉", 20)';
    this.executeSql(sql_data01);
    this.executeSql(sql_data02);
},
/**
 * 查询数据
 */
queryTable(){
    const sql = "SELECT * FROM zbtestTable";
    this.executeSql(sql);
}
}

结果:

image.png
四. indexDB - 索引数据库(可以用保存数据/离线缓存)
  1. 特点:

A. 异步API-请求-响应的模式保存与获取数据,不是我们熟悉的key-value,但是存储方式键值对储存;
B.索引数据库没有表的概念,但是有objectStore, 就是类似于数据库的表
C.一个数据库中可以包含多个objectStore
D. 不属于关系型数据库(不支持 SQL 查询语句)
E. 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限
F.同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库

2.对象的基本概念

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

  1. 操作流程

3.1 创建或者打开数据库

/**
 * indexDB对象
 */
indexDBOptions:{
    name: "test-01",
    version: 1,
    db:null,
},
   /**
 * 打开数据库
 */
openIndexDB(storeName, callBack){
    if(!this.indexDBOptions.db){
        // open(name: string, version?: number)  name 数据库的名字 version: 版本
        const dbRequest = window.indexedDB.open(this.indexDBOptions.name, this.indexDBOptions.version);
        const that = this;

        // success事件表示成功打开数据库。
        dbRequest.onsuccess = function (event) {
            const db = event.target.result;
            that.indexDBOptions.db = db;
            if(callBack){
                callBack();
            }
        };
        //  error事件表示打开数据库失败
        dbRequest.onerror = function (event) {
            console.log("数据库打开失败--------" || e.currentTarget.error.message);
        };

        // 如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
        dbRequest.onupgradeneeded=function(e){
            const db = e.target.result;
            console.log('DB version changed to '+ db.version);
            if(!db.objectStoreNames.contains(storeName)){
                // 创建store 新建对象仓库(即新建表) 主键是id
                const store = db.createObjectStore(storeName, {keyPath:"id"});

                // 创建索引 IDBObject.createIndex()的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)
                store.createIndex('nameIndex', 'name', {unique: true});
                store.createIndex('ageIndex', 'age', {unique: true});

            }

        };
    }
},

3.2 写入数据

  /**
 * 获取数据表 objectStore
 */
getStore(storeName, callBack){
    
    if(this.indexDBOptions.db){
        // "readonly" | "readwrite" | "versionchange" 三种方式
        const transation = this.indexDBOptions.db.transaction(storeName, "readwrite");
        const store = transation.objectStore(storeName);
        if(callBack){
            callBack(store);
        }
    }else {
        const that = this;
        this.openIndexDB(storeName, function () {
            that.getStore(storeName, callBack);
        });
    }
},

 /**
 * 保存数据
 */
addDataToDB(storeName, dataList){
    const that = this;
    // 1. 创建表
    this.openIndexDB(storeName ,function () {
        // 2. 获取表
       that.getStore(storeName, function (store) {
            // 3.保存数据
            for (let i = 0; i < dataList.length; i++) {
                const item = dataList[I];
                store.add(item);
            }
        });
    });
},
  const persons = [
{
    id: 1000,
    name: "亚瑟",
    age: 15,
},
{
    id: 1001,
    name: "安其拉",
    age: 20,
},
{
    id: 1002,
    name: "典韦",
    age: 18,
 }
];
//  IndexDBHepler 工具类
IndexDBHepler.openIndexDB('person-test');
IndexDBHepler.addDataToDB('person-test',persons);

结果:

image.png

3.3 获取数据 - 三种方式

  • 通过key获取数据

  • 通过索引获取数据

  • 通过游标获取

    /**
    * 获取数据 通过key
    */
    getDataByKeyFromDB(storeName, key, callBack){
      // 1. 获取表
      this.getStore(storeName, function (store) {
         const request=  store.get(key)
    
         request.onsuccess = function (e) {
             const person=e.target.result;
             if(callBack){
                 callBack(person);
             }
             console.log("通过key获取数据----",person);
         }
      });
     },
      /**
       * 通过索引获取数据
       */
      getDataByIndexFromDB(storeName, indexName, value, callBack){
      this.getStore(storeName, function (store) {
          const index = store.index(indexName);
          const request = index.get(value);
          request.onsuccess = function (e) {
              const person=e.target.result;
              if(callBack){
                  callBack(person);
              }
              console.log("通过Index获取数据----",person);
          }
      });
     },
      /**
       * 通过游标获取
       */
      getDataByCursorFromDB(storeName, callBack){
         this.getStore(storeName, function (store) {
          const request = store.openCursor();
          request.onsuccess = function (e) {
              const cursor=e.target.result;
              if(cursor){
                  const person=cursor.value;
                  console.log('通过游标获取---',person);
                  cursor.continue();
              }
          }
      });
     },
    

3.4关闭和删除数据库

 /**
 * 关闭数据库
 */
closeIndexDB(){
    if(this.indexDBOptions.db){
        this.indexDBOptions.db.close();
    }
},
/**
 * 删除数据库
 */
deleteIndexDB(name){
    const indexName = this.indexDBOptions.name || name;
    this.closeIndexDB();
    window.indexedDB.deleteDatabase(indexName);
}
  1. 使用场景

存储大量数据,比如页面离线缓存等

最后,以上总结都是老夫为了方便理解自己总结的内容,其中参考以下资料,创作不易,尊重原著

参考文献链接
http://www.ruanyifeng.com/blog/2018/07/indexeddb.html

H5系列
Web前端基础篇-HTML-01-BOM浏览器对象模型
Web前端基础篇-HTML-02-HTML的生命周期
Web前端基础篇-HTML-03-事件处理系统
Web前端基础篇-HTML-04-HTML 渲染流程
Web前端基础篇-HTML5-05-最全本地存储总结
Web前端基础篇-HTML5-06-离线缓存AppCache
Web前端基础篇-HTML5-07-浏览器缓存机制