转载请注明出处:https://github.com/thx/magix/...javascript
在前端开发过程当中,咱们常常会在内存中缓存一些数据,其实javascript的缓存比较简单,只须要声明一个变量或把一些数据挂到某个对象上便可,好比咱们要实现一个对全部的ajax请求缓存的方法,简单实现以下:前端
var cache={}; var request=function(url,callback){ if(cache[url]){ callback(cache[url]); }else{ $.ajax({ url:url, success:function(data){ callback(cache[url]=data); } }); } };
注意
示例中仅作简单演示,未考虑同时对相同的url请求屡次,好比java
request('/a'); request('/a');
在上述代码中仍然会发起2次对a的请求,这不是咱们讨论的重点,咱们重点讨论请求成功并缓存数据后,再请求该url的事情,因此这个问题略过不题git
咱们回头看一下咱们的request方法,会发现这样的问题:github
有些url在整个项目中或许只请求一次,咱们仍然对它的结果进行缓存,形成资源被白白占用,若是应用在移动端,移动端的内存资源自己就比较宝贵,因此咱们不能浪费ajax
因此针对request方法中的缓存作一些改进,使它更智能些。咱们须要一种算法,保证缓存的个数不能太多,同时缓存的资源数超多时,它能聪明的删掉那些不经常使用的缓存数据算法
那咱们看一下,当咱们要实现这样一个算法有哪些关键点要考虑:api
咱们须要知道缓存中缓存了多少个资源数组
当咱们从缓存中获取某个缓存资源时,获取的算法复杂度应该是o(1),缓存模块的做用是提升程序的效率,拿空间换时间,因此缓存模块不该该占用过多的CPU时间缓存
明确目标后,咱们就须要寻找合适的对象来缓存咱们的数据:
var obj={}
根据key从obj上查找某个对象,复杂度是o(1),知足咱们的第2条要求,但obj上缓存了多少个资源须要咱们自已维护
var obj=[]
根据key查找某个对象时,复杂度是o(n),但数组有length,能够自动的帮咱们维护当前缓存了多少个资源
咱们知道数组是特殊的对象,因此咱们能够把数组当成普通的对象来用。
当咱们把一个缓存对象push进数组时,再根据缓存对象惟一的key,把它放到这个数组对象上
因此这时候咱们第1版本的代码可能相似这样:
var Cache=function(){ this.$cache=[]; }; Cache.prototype.set=function(key,item){ var cache=this.$cache; var wrap={//包装一次,方便咱们放其它信息,同时利用对象引用传递 key:key, item:item }; cache.push(wrap); cache['cache_'+key]=wrap;//加上cache_的缘由是:防止key是数字或可转化为数字的字符串,这样的话就变成了如 cache['2'] 经过下标访问数组里面的元素了。 }; Cache.prototype.get=function(key){ var res=this.$cache['cache_'+key]; return res.item;//返回放入的资源 };
使用示例以下:
var c=new Cache(); c.set('/api/userinfo',{ name:'彳刂' }); console.log(c.get('/api/userinfo'));
这时候咱们就完成了初步要求,知道缓存个数,查找时复杂度是o(1)
不过咱们仍然须要更智能一些的缓存:
知道单个缓存资源的使用频率
知道单个缓存资源的最后使用时间
缓存中最多能放多少个缓存资源
什么时候清理缓存资源
咱们改造下刚才的代码:
var Cache=function(max){ this.$cache=[]; + this.$max=max | 0 ||20; }; Cache.prototype.set=function(key,item){ var cache=this.$cache; - var wrap={//包装一次,方便咱们放其它信息,同时利用对象引用传递 - key:key, - item:item - }; + key='cache_'+key; + var wrap=cache[key]; + if(!cache.hasOwnProperty(key){ + wrap={}; + cache.push(wrap); + cache[key]=wrap; + } + wrap.item=item; + wrap.fre=1;//初始使用频率为1 + wrap.key=key; + wrap.time=new Date().getTime(); }; Cache.prototype.get=function(key){ var res=this.$cache['cache_'+key]; if(res){ res.fre++;//更新使用频率 res.time=new Date().getTime(); } return res.item;//返回放入的资源 };
在咱们第2版本的代码中,咱们添加了最多缓存资源数max,同时每一个缓存资源加入了使用频率fre及最后使用时间time,同时咱们修改了set方法,考虑了相同key的屡次set问题。
咱们简单测试下:
var c=new Cache(); c.set('/api/userinfo',{ name:'彳刂' }); console.log(c.$cache[0].fre);//1 console.log(c.get('/api/userinfo')); console.log(c.$cache[0].fre);//2
接下来咱们要考虑一但缓存资源数超出了咱们规定的max时,咱们要清理掉不经常使用的资源。清理时咱们根据频率的使用fre标志,fre最小的优先清理,同时相同的fre,咱们优先清理time比较小的,这也是time设计的意义所在。
因此第3版咱们的代码以下:
var Cache=function(max){ this.$cache=[]; this.$max=max | 0 ||20; }; Cache.prototype.set=function(key,item){ var cache=this.$cache; key='cache_'+key; var wrap=cache[key]; if(!cache.hasOwnProperty(key){ + if(cache.length>=this.$max){ + cache.sort(function(a,b){ + return b.fre==a.fre?b.time-a.time:b.fre-a.fre; + }); + var item=cache.pop();//删除频率使用最小,时间最先的1个 + delete cache[item.key];// + } wrap={}; cache.push(wrap); cache[key]=wrap; } wrap.item=item; wrap.fre=1;//初始使用频率为1 wrap.key=key; wrap.time=new Date().getTime(); }; Cache.prototype.get=function(key){ var res=this.$cache['cache_'+key]; if(res){ res.fre++;//更新使用频率 res.time=new Date().getTime(); } return res.item;//返回放入的资源 }; +Cache.prototype.has=funciton(key){ + return this.$cache.hasOwnProperty('cache_'+key); +};
OK,到这里咱们就完成了想要的缓存,咱们结合最开始的request方法来进行实际测试:
var cache=new Cache(2); var request=function(url,callback){ if(cache.has(url)){ callback(cache.get(url); }else{ $.ajax({ url:url, success:function(data){ cache.set(url,data); callback(data); } }); } }) }; //实际使用(假设下一个request方法被调用时,前面request的已经完成请求并缓存好了数据): request('/api/item1'); request('/api/item2'); request('/api/item1');//命中缓存 request('/api/item3');//达到上限2,cache对象的内部$cache排序一次,删除/api/item2的缓存 request('/api/item4');//仍然达到上限2,cache对象的内部$cache排序一次,删除/api/item3的缓存 request('/api/item3');//接下来须要屡次使用/api/item3,但在请求/api/item4时,它已经被删除了,因此咱们须要从新请求。完成请求后,由于上限2依然知足,因此cache对象内部的$cache仍然须要排序一次,删除/api/item4 request('/api/item3');//命中缓存
根据上述使用,咱们发现,一但达到缓存的上限后,带来的问题以下:
新的缓存资源进来一个,就须要从新排序一次,性能很差
有可能误删除接下来可能频率使用到的缓存资源
这时候咱们就须要寻找突破。类比咱们常常使用的操做系统的缓存区,咱们的缓存是否也能够加入一个缓冲区呢?当整个缓存列表加上缓冲区都满的时候,才清空一次缓存区,不但能解决频繁排序的问题,也能很好的保留接下来程序中可能频繁使用到的缓存资源
来,缓存的第4版:
var Cache=function(max,buffer){ this.$cache=[]; this.$max=max | 0 ||20; + this.$buffer=buffer | 0 ||5; }; Cache.prototype.set=function(key,item){ var cache=this.$cache; key='cache_'+key; var wrap=cache[key]; if(!cache.hasOwnProperty(key){ - if(cache.length>=this.$max){ + if(cache.length>=this.$max+this.$buffer){ cache.sort(function(a,b){ return b.fre==a.fre?b.time-a.time:b.fre-a.fre; }); - var item=cache.pop();//删除频率使用最小,时间最先的1个 - delete cache[item.key];// + var buffer=this.$buffer; + while(buffer--){ + var item=cache.pop(); + delete cache[item.key]; + } } wrap={}; cache.push(wrap); cache[key]=wrap; } wrap.item=item; wrap.fre=1;//初始使用频率为1 wrap.key=key; wrap.time=new Date().getTime(); }; Cache.prototype.get=function(key){ var res=this.$cache['cache_'+key]; if(res){ res.fre++;//更新使用频率 res.time=new Date().getTime(); } return res.item;//返回放入的资源 }; Cache.prototype.has=funciton(key){ return this.$cache.hasOwnProperty('cache_'+key); };
这时候咱们再结合request来测试一下:
var cache=new Cache(2,2);//最大2个,2个缓存区,真实能够缓存4个 var request=function(url,callback){ if(cache.has(url)){ callback(cache.get(url); }else{ //$.ajax略 } }; request('/api/item1'); request('/api/item2'); request('/api/item3');//放在缓冲区 request('/api/item4');//放在缓冲区 request('/api/item5');//排序一次,清除/api/item2 /api/item1 request('/api/item6');//放在缓冲区 request('/api/item7');//放在缓冲区
至此咱们就完成了比较完善的缓存模块
固然,后续咱们增长缓存资源的生命期,好比20分钟后清除,也是较容易的,不在这里详解。
Magix的Cache模块比这里稍微再复杂些,不过原理都是同样的。
Magix是一个区块管理框架,项目地址在这里magix
区块介绍在这里magix区块介绍