什么是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: 将数据保存在客户端硬件设备上,不管它是什么,意思就是下次打开计算机时候数据还在。
两者区别就是一个作为临时保存,一个长期保存。
这里来一段简单的代码说明其基本使用:
XML/HTML Code复制内容到剪贴板
<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的使用一般有以下需求:
① 缓存一般信息,如搜索页的出发城市,达到城市,非实时定位信息
② 缓存城市列表数据,这个数据往往比较大
③ 每条缓存信息需要可追踪,比如服务器通知城市数据更新,这个时候在最近一次访问的时候要自动设置过期
④ 每条信息具有过期日期状态,在过期外时间需要由服务器拉取数据
XML/HTML Code复制内容到剪贴板
define([],function(){varStorage=_.inherit({//默认属性propertys:function(){//代理对象,默认为localstoragethis.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();},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的基本操作,并且对以上问题做了处理,而真实的使用还要再抽象:
XML/HTML Code复制内容到剪贴板
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中加入:
XML/HTML Code复制内容到剪贴板
window.onunload=function(){};//适合单页应用,不要问我为什么,我也不知道 结语
localstorage是移动开发必不可少的技术点,需要深入了解,具体业务代码后续会放到git上,有兴趣的朋友可以去了解