轉載請註明出處: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區塊介紹