• HTML5本地存储Localstorage


    什么是localstorage

    前几天在老项目中发现有对cookie的操作觉得很奇怪,咨询下来是要缓存一些信息,以避免在URL上面传递参数,但没有考虑过cookie会带来什么问题:

    ① cookie大小限制在4k左右,不适合存业务数据
    ② cookie每次随HTTP事务一起发送,浪费带宽

    我们是做移动项目的,所以这里真实适合使用的技术是localstorage,localstorage可以说是对cookie的优化,使用它可以方便在客户端存储数据,并且不会随着HTTP传输,但也不是没有问题:

    ① localstorage大小限制在500万字符左右,各个浏览器不一致
    ② localstorage在隐私模式下不可读取
    ③ localstorage本质是在读写文件,数据多的话会比较卡(firefox会一次性将数据导入内存,想想就觉得吓人啊)
    ④ localstorage不能被爬虫爬取,不要用它完全取代URL传参

    瑕不掩瑜,以上问题皆可避免,所以我们的关注点应该放在如何使用localstorage上,并且是如何正确使用。

    localstorage的使用

    基础知识

    localstorage存储对象分为两种:

    ① sessionStrage: session即会话的意思,在这里的session是指用户浏览某个网站时,从进入网站到关闭网站这个时间段,session对象的有效期就只有这么长。

    ② localStorage: 将数据保存在客户端硬件设备上,不管它是什么,意思就是下次打开计算机时候数据还在。

    两者区别就是一个作为临时保存,一个长期保存。

    这里来一段简单的代码说明其基本使用:

     1 <div id="msg" style="margin: 10px 0; border: 1px solid black; padding: 10px;  300px;
     2   height: 100px;">
     3 </div>
     4 <input type="text" id="text" />
     5 <select id="type">
     6   <option value="session">sessionStorage</option>
     7   <option value="local">localStorage</option>
     8 </select>
     9 <button onclick="save();">
    10   保存数据</button>
    11 <button onclick="load();">
    12   读取数据</button>
    13 <script type="text/javascript">
    14   var msg = document.getElementById('msg'),
    15             text = document.getElementById('text'),
    16             type = document.getElementById('type');
    17 
    18   function save() {
    19     var str = text.value;
    20     var t = type.value;
    21     if (t == 'session') {
    22       sessionStorage.setItem('msg', str);
    23     } else {
    24       localStorage.setItem('msg', str);
    25     }
    26   }
    27 
    28   function load() {
    29     var t = type.value;
    30     if (t == 'session') {
    31       msg.innerHTML = sessionStorage.getItem('msg');
    32     } else {
    33       msg.innerHTML = localStorage.getItem('msg');
    34     }
    35   }
    36 </script>

    真实场景

    实际工作中对localstorage的使用一般有以下需求:

    ① 缓存一般信息,如搜索页的出发城市,达到城市,非实时定位信息

    ② 缓存城市列表数据,这个数据往往比较大

    ③ 每条缓存信息需要可追踪,比如服务器通知城市数据更新,这个时候在最近一次访问的时候要自动设置过期

    ④ 每条信息具有过期日期状态,在过期外时间需要由服务器拉取数据

    ⑤ ......

      1 define([], function () {
      2 
      3   var Storage = _.inherit({
      4     //默认属性
      5     propertys: function () {
      6 
      7       //代理对象,默认为localstorage
      8       this.sProxy = window.localStorage;
      9 
     10       //60 * 60 * 24 * 30 * 1000 ms ==30天
     11       this.defaultLifeTime = 2592000000;
     12 
     13       //本地缓存用以存放所有localstorage键值与过期日期的映射
     14       this.keyCache = 'SYSTEM_KEY_TIMEOUT_MAP';
     15 
     16       //当缓存容量已满,每次删除的缓存数
     17       this.removeNum = 5;
     18 
     19     },
     20 
     21     assert: function () {
     22       if (this.sProxy === null) {
     23         throw 'not override sProxy property';
     24       }
     25     },
     26 
     27     initialize: function (opts) {
     28       this.propertys();
     29       this.assert();
     30     },
     31 
     32     /*
     33     新增localstorage
     34     数据格式包括唯一键值,json字符串,过期日期,存入日期
     35     sign 为格式化后的请求参数,用于同一请求不同参数时候返回新数据,比如列表为北京的城市,后切换为上海,会判断tag不同而更新缓存数据,tag相当于签名
     36     每一键值只会缓存一条信息
     37     */
     38     set: function (key, value, timeout, sign) {
     39       var _d = new Date();
     40       //存入日期
     41       var indate = _d.getTime();
     42 
     43       //最终保存的数据
     44       var entity = null;
     45 
     46       if (!timeout) {
     47         _d.setTime(_d.getTime() + this.defaultLifeTime);
     48         timeout = _d.getTime();
     49       }
     50 
     51       //
     52       this.setKeyCache(key, timeout);
     53       entity = this.buildStorageObj(value, indate, timeout, sign);
     54 
     55       try {
     56         this.sProxy.setItem(key, JSON.stringify(entity));
     57         return true;
     58       } catch (e) {
     59         //localstorage写满时,全清掉
     60         if (e.name == 'QuotaExceededError') {
     61           //            this.sProxy.clear();
     62           //localstorage写满时,选择离过期时间最近的数据删除,这样也会有些影响,但是感觉比全清除好些,如果缓存过多,此过程比较耗时,100ms以内
     63           if (!this.removeLastCache()) throw '本次数据存储量过大';
     64           this.set(key, value, timeout, sign);
     65         }
     66         console && console.log(e);
     67       }
     68       return false;
     69     },
     70 
     71     //删除过期缓存
     72     removeOverdueCache: function () {
     73       var tmpObj = null, i, len;
     74 
     75       var now = new Date().getTime();
     76       //取出键值对
     77       var cacheStr = this.sProxy.getItem(this.keyCache);
     78       var cacheMap = [];
     79       var newMap = [];
     80       if (!cacheStr) {
     81         return;
     82       }
     83 
     84       cacheMap = JSON.parse(cacheStr);
     85 
     86       for (i = 0, len = cacheMap.length; i < len; i++) {
     87         tmpObj = cacheMap[i];
     88         if (tmpObj.timeout < now) {
     89           this.sProxy.removeItem(tmpObj.key);
     90         } else {
     91           newMap.push(tmpObj);
     92         }
     93       }
     94       this.sProxy.setItem(this.keyCache, JSON.stringify(newMap));
     95 
     96     },
     97 
     98     removeLastCache: function () {
     99       var i, len;
    100       var num = this.removeNum || 5;
    101 
    102       //取出键值对
    103       var cacheStr = this.sProxy.getItem(this.keyCache);
    104       var cacheMap = [];
    105       var delMap = [];
    106 
    107       //说明本次存储过大
    108       if (!cacheStr) return false;
    109 
    110       cacheMap.sort(function (a, b) {
    111         return a.timeout - b.timeout;
    112       });
    113 
    114       //删除了哪些数据
    115       delMap = cacheMap.splice(0, num);
    116       for (i = 0, len = delMap.length; i < len; i++) {
    117         this.sProxy.removeItem(delMap[i].key);
    118       }
    119 
    120       this.sProxy.setItem(this.keyCache, JSON.stringify(cacheMap));
    121       return true;
    122     },
    123 
    124     setKeyCache: function (key, timeout) {
    125       if (!key || !timeout || timeout < new Date().getTime()) return;
    126       var i, len, tmpObj;
    127 
    128       //获取当前已经缓存的键值字符串
    129       var oldstr = this.sProxy.getItem(this.keyCache);
    130       var oldMap = [];
    131       //当前key是否已经存在
    132       var flag = false;
    133       var obj = {};
    134       obj.key = key;
    135       obj.timeout = timeout;
    136 
    137       if (oldstr) {
    138         oldMap = JSON.parse(oldstr);
    139         if (!_.isArray(oldMap)) oldMap = [];
    140       }
    141 
    142       for (i = 0, len = oldMap.length; i < len; i++) {
    143         tmpObj = oldMap[i];
    144         if (tmpObj.key == key) {
    145           oldMap[i] = obj;
    146           flag = true;
    147           break;
    148         }
    149       }
    150       if (!flag) oldMap.push(obj);
    151       //最后将新数组放到缓存中
    152       this.sProxy.setItem(this.keyCache, JSON.stringify(oldMap));
    153 
    154     },
    155 
    156     buildStorageObj: function (value, indate, timeout, sign) {
    157       var obj = {
    158         value: value,
    159         timeout: timeout,
    160         sign: sign,
    161         indate: indate
    162       };
    163       return obj;
    164     },
    165 
    166     get: function (key, sign) {
    167       var result, now = new Date().getTime();
    168       try {
    169         result = this.sProxy.getItem(key);
    170         if (!result) return null;
    171         result = JSON.parse(result);
    172 
    173         //数据过期
    174         if (result.timeout < now) return null;
    175 
    176         //需要验证签名
    177         if (sign) {
    178           if (sign === result.sign)
    179             return result.value;
    180           return null;
    181         } else {
    182           return result.value;
    183         }
    184 
    185       } catch (e) {
    186         console && console.log(e);
    187       }
    188       return null;
    189     },
    190 
    191     //获取签名
    192     getSign: function (key) {
    193       var result, sign = null;
    194       try {
    195         result = this.sProxy.getItem(key);
    196         if (result) {
    197           result = JSON.parse(result);
    198           sign = result && result.sign
    199         }
    200       } catch (e) {
    201         console && console.log(e);
    202       }
    203       return sign;
    204     },
    205 
    206     remove: function (key) {
    207       return this.sProxy.removeItem(key);
    208     },
    209 
    210     clear: function () {
    211       this.sProxy.clear();
    212     }
    213   });
    214 
    215   Storage.getInstance = function () {
    216     if (this.instance) {
    217       return this.instance;
    218     } else {
    219       return this.instance = new this();
    220     }
    221   };
    222 
    223   return Storage;
    224 
    225 });
    View Code

    这段代码包含了localstorage的基本操作,并且对以上问题做了处理,而真实的使用还要再抽象:

      1 define(['AbstractStorage'], function (AbstractStorage) {
      2 
      3   var Store = _.inherit({
      4     //默认属性
      5     propertys: function () {
      6 
      7       //每个对象一定要具有存储键,并且不能重复
      8       this.key = null;
      9 
     10       //默认一条数据的生命周期,S为秒,M为分,D为天
     11       this.lifeTime = '30M';
     12 
     13       //默认返回数据
     14       //      this.defaultData = null;
     15 
     16       //代理对象,localstorage对象
     17       this.sProxy = new AbstractStorage();
     18 
     19     },
     20 
     21     setOption: function (options) {
     22       _.extend(this, options);
     23     },
     24 
     25     assert: function () {
     26       if (this.key === null) {
     27         throw 'not override key property';
     28       }
     29       if (this.sProxy === null) {
     30         throw 'not override sProxy property';
     31       }
     32     },
     33 
     34     initialize: function (opts) {
     35       this.propertys();
     36       this.setOption(opts);
     37       this.assert();
     38     },
     39 
     40     _getLifeTime: function () {
     41       var timeout = 0;
     42       var str = this.lifeTime;
     43       var unit = str.charAt(str.length - 1);
     44       var num = str.substring(0, str.length - 1);
     45       var Map = {
     46         D: 86400,
     47         H: 3600,
     48         M: 60,
     49         S: 1
     50       };
     51       if (typeof unit == 'string') {
     52         unit = unit.toUpperCase();
     53       }
     54       timeout = num;
     55       if (unit) timeout = Map[unit];
     56 
     57       //单位为毫秒
     58       return num * timeout * 1000 ;
     59     },
     60 
     61     //缓存数据
     62     set: function (value, sign) {
     63       //获取过期时间
     64       var timeout = new Date();
     65       timeout.setTime(timeout.getTime() + this._getLifeTime());
     66       this.sProxy.set(this.key, value, timeout.getTime(), sign);
     67     },
     68 
     69     //设置单个属性
     70     setAttr: function (name, value, sign) {
     71       var key, obj;
     72       if (_.isObject(name)) {
     73         for (key in name) {
     74           if (name.hasOwnProperty(key)) this.setAttr(k, name[k], value);
     75         }
     76         return;
     77       }
     78 
     79       if (!sign) sign = this.getSign();
     80 
     81       //获取当前对象
     82       obj = this.get(sign) || {};
     83       if (!obj) return;
     84       obj[name] = value;
     85       this.set(obj, sign);
     86 
     87     },
     88 
     89     getSign: function () {
     90       return this.sProxy.getSign(this.key);
     91     },
     92 
     93     remove: function () {
     94       this.sProxy.remove(this.key);
     95     },
     96 
     97     removeAttr: function (attrName) {
     98       var obj = this.get() || {};
     99       if (obj[attrName]) {
    100         delete obj[attrName];
    101       }
    102       this.set(obj);
    103     },
    104 
    105     get: function (sign) {
    106       var result = [], isEmpty = true, a;
    107       var obj = this.sProxy.get(this.key, sign);
    108       var type = typeof obj;
    109       var o = { 'string': true, 'number': true, 'boolean': true };
    110       if (o[type]) return obj;
    111 
    112       if (_.isArray(obj)) {
    113         for (var i = 0, len = obj.length; i < len; i++) {
    114           result[i] = obj[i];
    115         }
    116       } else if (_.isObject(obj)) {
    117         result = obj;
    118       }
    119 
    120       for (a in result) {
    121         isEmpty = false;
    122         break;
    123       }
    124       return !isEmpty ? result : null;
    125     },
    126 
    127     getAttr: function (attrName, tag) {
    128       var obj = this.get(tag);
    129       var attrVal = null;
    130       if (obj) {
    131         attrVal = obj[attrName];
    132       }
    133       return attrVal;
    134     }
    135 
    136   });
    137 
    138   Store.getInstance = function () {
    139     if (this.instance) {
    140       return this.instance;
    141     } else {
    142       return this.instance = new this();
    143     }
    144   };
    145 
    146   return Store;
    147 });
    View Code

    我们真实使用的时候是使用store这个类操作localstorage,代码结束简单测试:

    存储完成,以后都不会走请求,于是今天的代码基本结束。

    其它

    隐私模式下可以采用window.name模拟sessionStorage的方式处理,因为window.name是可做保存的,这个也是其解决跨域方案的原因。

    在android Hybrid中有一后退按钮,此按钮一旦按下会回到上一个页面,这个时候里面的localstorage可能会读取失效!一个简单不靠谱的解决方案是在webapp中加入:

    window.onunload = function () { };//适合单页应用,不要问我为什么,我也不知道

    结语

    localstorage是移动开发必不可少的技术点,需要深入了解,具体业务代码后续会放到git上,有兴趣的朋友可以去了解
  • 相关阅读:
    【题解】直线交点数
    【题解】[TJOI2010] 阅读理解
    清北学堂 2020 国庆J2考前综合强化 Day7
    清北学堂 2020 国庆J2考前综合强化 Day6
    清北学堂 2020 国庆J2考前综合强化 Day5
    清北学堂 2020 国庆J2考前综合强化 Day4
    清北学堂 2020 国庆J2考前综合强化 Day3
    test
    清北学堂 2020 国庆J2考前综合强化 Day2
    清北学堂 2020 国庆J2考前综合强化 Day1
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/4509472.html
Copyright © 2020-2023  润新知