什么是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: 将数据保存在客户端硬件设备上,不管它是什么,意思就是下次打开计算机时候数据还在。
两者区别就是一个作为临时保存,一个长期保存。
这里来一段简单的代码说明其基本使用:
- <divid="msg"style="margin:10px0;border:1pxsolidblack;padding:10px;width:300px;
- height:100px;">
- </div>
- <inputtype="text"id="text"/>
- <selectid="type">
- <optionvalue="session">sessionStorage</option>
- <optionvalue="local">localStorage</option>
- </select>
- <buttononclick="save();">
- 保存数据</button>
- <buttononclick="load();">
- 读取数据</button>
- <scripttype="text/javascript">
- varmsg=document.getElementById('msg'),
- text=document.getElementById('text'),
- type=document.getElementById('type');
- functionsave(){
- varstr=text.value;
- vart=type.value;
- if(t=='session'){
- sessionStorage.setItem('msg',str);
- }else{
- localStorage.setItem('msg',str);
- }
- }
- functionload(){
- vart=type.value;
- if(t=='session'){
- msg.innerHTML=sessionStorage.getItem('msg');
- }else{
- msg.innerHTML=localStorage.getItem('msg');
- }
- }
- </script>
真实场景
实际工作中对localstorage的使用一般有以下需求:
① 缓存一般信息,如搜索页的出发城市,达到城市,非实时定位信息
② 缓存城市列表数据,这个数据往往比较大
③ 每条缓存信息需要可追踪,比如服务器通知城市数据更新,这个时候在最近一次访问的时候要自动设置过期
④ 每条信息具有过期日期状态,在过期外时间需要由服务器拉取数据
- define([],function(){
- varStorage=_.inherit({
- //默认属性
- propertys:function(){
- //代理对象,默认为localstorage
- this.sProxy=window.localStorage;
- //60*60*24*30*1000ms==30天
- this.defaultLifeTime=2592000000;
- //本地缓存用以存放所有localstorage键值与过期日期的映射
- this.keyCache='SYSTEM_KEY_TIMEOUT_MAP';
- //当缓存容量已满,每次删除的缓存数
- this.removeNum=5;
- },
- assert:function(){
- if(this.sProxy===null){
- throw'notoverridesProxyproperty';
- }
- },
- initialize:function(opts){
- this.propertys();
- this.assert();
- },
- /*
- 新增localstorage
- 数据格式包括唯一键值,json字符串,过期日期,存入日期
- sign为格式化后的请求参数,用于同一请求不同参数时候返回新数据,比如列表为北京的城市,后切换为上海,会判断tag不同而更新缓存数据,tag相当于签名
- 每一键值只会缓存一条信息
- */
- set:function(key,value,timeout,sign){
- var_d=newDate();
- //存入日期
- varindate=_d.getTime();
- //最终保存的数据
- varentity=null;
- if(!timeout){
- _d.setTime(_d.getTime()+this.defaultLifeTime);
- timeout=_d.getTime();
- }
- //
- this.setKeyCache(key,timeout);
- entity=this.buildStorageObj(value,indate,timeout,sign);
- try{
- this.sProxy.setItem(key,JSON.stringify(entity));
- returntrue;
- }catch(e){
- //localstorage写满时,全清掉
- if(e.name=='QuotaExceededError'){
- //this.sProxy.clear();
- //localstorage写满时,选择离过期时间最近的数据删除,这样也会有些影响,但是感觉比全清除好些,如果缓存过多,此过程比较耗时,100ms以内
- if(!this.removeLastCache())throw'本次数据存储量过大';
- this.set(key,value,timeout,sign);
- }
- console&&console.log(e);
- }
- returnfalse;
- },
- //删除过期缓存
- removeOverdueCache:function(){
- vartmpObj=null,i,len;
- varnow=newDate().getTime();
- //取出键值对
- varcacheStr=this.sProxy.getItem(this.keyCache);
- varcacheMap=[];
- varnewMap=[];
- if(!cacheStr){
- return;
- }
- cacheMap=JSON.parse(cacheStr);
- for(i=0,len=cacheMap.length;i<len;i++){
- tmpObj=cacheMap[i];
- if(tmpObj.timeout<now){
- this.sProxy.removeItem(tmpObj.key);
- }else{
- newMap.push(tmpObj);
- }
- }
- this.sProxy.setItem(this.keyCache,JSON.stringify(newMap));
- },
- removeLastCache:function(){
- vari,len;
- varnum=this.removeNum||5;
- //取出键值对
- varcacheStr=this.sProxy.getItem(this.keyCache);
- varcacheMap=[];
- vardelMap=[];
- //说明本次存储过大
- if(!cacheStr)returnfalse;
- cacheMap.sort(function(a,b){
- returna.timeout-b.timeout;
- });
- //删除了哪些数据
- delMap=cacheMap.splice(0,num);
- for(i=0,len=delMap.length;i<len;i++){
- this.sProxy.removeItem(delMap[i].key);
- }
- this.sProxy.setItem(this.keyCache,JSON.stringify(cacheMap));
- returntrue;
- },
- setKeyCache:function(key,timeout){
- if(!key||!timeout||timeout<newDate().getTime())return;
- vari,len,tmpObj;
- //获取当前已经缓存的键值字符串
- varoldstr=this.sProxy.getItem(this.keyCache);
- varoldMap=[];
- //当前key是否已经存在
- varflag=false;
- varobj={};
- obj.key=key;
- obj.timeout=timeout;
- if(oldstr){
- oldMap=JSON.parse(oldstr);
- if(!_.isArray(oldMap))oldMap=[];
- }
- for(i=0,len=oldMap.length;i<len;i++){
- tmpObj=oldMap[i];
- if(tmpObj.key==key){
- oldMap[i]=obj;
- flag=true;
- break;
- }
- }
- if(!flag)oldMap.push(obj);
- //最后将新数组放到缓存中
- this.sProxy.setItem(this.keyCache,JSON.stringify(oldMap));
- },
- buildStorageObj:function(value,indate,timeout,sign){
- varobj={
- value:value,
- timeout:timeout,
- sign:sign,
- indate:indate
- };
- returnobj;
- },
- get:function(key,sign){
- varresult,now=newDate().getTime();
- try{
- result=this.sProxy.getItem(key);
- if(!result)returnnull;
- result=JSON.parse(result);
- //数据过期
- if(result.timeout<now)returnnull;
- //需要验证签名
- if(sign){
- if(sign===result.sign)
- returnresult.value;
- returnnull;
- }else{
- returnresult.value;
- }
- }catch(e){
- console&&console.log(e);
- }
- returnnull;
- },
- //获取签名
- getSign:function(key){
- varresult,sign=null;
- try{
- result=this.sProxy.getItem(key);
- if(result){
- result=JSON.parse(result);
- sign=result&&result.sign
- }
- }catch(e){
- console&&console.log(e);
- }
- returnsign;
- },
- remove:function(key){
- returnthis.sProxy.removeItem(key);
- },
- clear:function(){
- this.sProxy.clear();
- }
- });
- Storage.getInstance=function(){
- if(this.instance){
- returnthis.instance;
- }else{
- returnthis.instance=newthis();
- }
- };
- returnStorage;
- });
这段代码包含了localstorage的基本操作,并且对以上问题做了处理,而真实的使用还要再抽象:
- define(['AbstractStorage'],function(AbstractStorage){
- varStore=_.inherit({
- //默认属性
- propertys:function(){
- //每个对象一定要具有存储键,并且不能重复
- this.key=null;
- //默认一条数据的生命周期,S为秒,M为分,D为天
- this.lifeTime='30M';
- //默认返回数据
- //this.defaultData=null;
- //代理对象,localstorage对象
- this.sProxy=newAbstractStorage();
- },
- setOption:function(options){
- _.extend(this,options);
- },
- assert:function(){
- if(this.key===null){
- throw'notoverridekeyproperty';
- }
- if(this.sProxy===null){
- throw'notoverridesProxyproperty';
- }
- },
- initialize:function(opts){
- this.propertys();
- this.setOption(opts);
- this.assert();
- },
- _getLifeTime:function(){
- vartimeout=0;
- varstr=this.lifeTime;
- varunit=str.charAt(str.length-1);
- varnum=str.substring(0,str.length-1);
- varMap={
- D:86400,
- H:3600,
- M:60,
- S:1
- };
- if(typeofunit=='string'){
- unitunit=unit.toUpperCase();
- }
- timeout=num;
- if(unit)timeout=Map[unit];
- //单位为毫秒
- returnnum*timeout*1000;
- },
- //缓存数据
- set:function(value,sign){
- //获取过期时间
- vartimeout=newDate();
- timeout.setTime(timeout.getTime()+this._getLifeTime());
- this.sProxy.set(this.key,value,timeout.getTime(),sign);
- },
- //设置单个属性
- setAttr:function(name,value,sign){
- varkey,obj;
- if(_.isObject(name)){
- for(keyinname){
- if(name.hasOwnProperty(key))this.setAttr(k,name[k],value);
- }
- return;
- }
- if(!sign)sign=this.getSign();
- //获取当前对象
- obj=this.get(sign)||{};
- if(!obj)return;
- obj[name]=value;
- this.set(obj,sign);
- },
- getSign:function(){
- returnthis.sProxy.getSign(this.key);
- },
- remove:function(){
- this.sProxy.remove(this.key);
- },
- removeAttr:function(attrName){
- varobj=this.get()||{};
- if(obj[attrName]){
- deleteobj[attrName];
- }
- this.set(obj);
- },
- get:function(sign){
- varresult=[],isEmpty=true,a;
- varobj=this.sProxy.get(this.key,sign);
- vartype=typeofobj;
- varo={'string':true,'number':true,'boolean':true};
- if(o[type])returnobj;
- if(_.isArray(obj)){
- for(vari=0,len=obj.length;i<len;i++){
- result[i]=obj[i];
- }
- }elseif(_.isObject(obj)){
- result=obj;
- }
- for(ainresult){
- isEmpty=false;
- break;
- }
- return!isEmpty?result:null;
- },
- getAttr:function(attrName,tag){
- varobj=this.get(tag);
- varattrVal=null;
- if(obj){
- attrVal=obj[attrName];
- }
- returnattrVal;
- }
- });
- Store.getInstance=function(){
- if(this.instance){
- returnthis.instance;
- }else{
- returnthis.instance=newthis();
- }
- };
- returnStore;
- });
我们真实使用的时候是使用store这个类操作localstorage,代码结束简单测试:
存储完成,以后都不会走请求,于是今天的代码基本结束 ,最后在android Hybrid中有一后退按钮,此按钮一旦按下会回到上一个页面,这个时候里面的localstorage可能会读取失效!一个简单不靠谱的解决方案是在webapp中加入:
- window.onunload=function(){};//适合单页应用,不要问我为什么,我也不知道
结语
localstorage是移动开发必不可少的技术点,需要深入了解,具体业务代码后续会放到git上,有兴趣的朋友可以去了解
本文HTML5中Localstorage的如何使用好代码教程到此结束。一个华丽短暂的梦,一个残酷漫长的现实。小编再次感谢大家对我们的支持!