要說誰在網站、web APP等前端應用中起到越來越重要的作用,那絕對很多人第一時間想到的就是:【緩存】!
面對越來越要求高質量、快應用的前端,緩存越來越成爲當之無愧的“無冕之王”!與其相關的,一直活躍在前端“視野”中的有兩個非常重要的應用:
- 【享元模式】中的對象池
- Ajax分頁中的信息緩存
請諸位隨筆者一探究竟:
筆者曾經提到過 Java 中 的 String 對象池,下面就來學習這種共享的技術:對象池維護一個裝載空閒對象的池子,如果需要對象的時候,不是直接 new,而是轉從對象池裏獲取。如果對象池裏沒有空閒對象,則創建一個新的對象,當獲取出的對象完成它的職責之後, 再進入池子等待被下次獲取。
對象池的原理很好理解,比如我們組人手一本《JavaScript權威指南》,從節約的角度來講,這並不是很划算,因爲大部分時間這些書都被閒置在各自的書架上,所以我們一開始就只買一本,或者一起建立一個小型圖書館(對象池),需要看書的時候就從圖書館裏借,看完了之後再把書還回圖書館。如果同時有三個人要看這本書,而現在圖書館裏只有兩本,那我們再馬上去書店買一本放入圖書館。
對象池技術的應用非常廣泛,HTTP 連接池和數據庫連接池都是其代表應用。在 Web 前端開發中,對象池使用最多的場景大概就是跟 DOM 有關的操作。很多空間和時間都消耗在了 DOM節點上,如何避免頻繁地創建和刪除 DOM 節點就成了一個有意義的話題。
比如:我們經常會在百度地圖的頁面上看到一個一個的小氣泡,網上將它們稱之爲“toolTip”,我們也暫且這麼稱呼。它們的作用是標記地址。
在地圖上搜索我家附近的時候,頁面裏出現了 2 個小氣泡。當我再搜索附近的漯河高中時,頁面中出現了 3個小氣泡。按照對象池的思想,在第二次搜索開始之前,並不會把第一次創建的2 個小氣泡刪除掉,而是把它們放進對象池。這樣在第二次的搜索結果頁面裏,我們只需要再創建 1 個小氣泡而不是 3 個。。。
通用對象池的實現:
var objectPoolFactory=function(createObjFn){
var objectPool=[]; //toolTip對象池
return {
create:function(){
var obj=objectPool.length===0 ? createObjFn.apply(this,arguments) : objectPool.shift();
return obj;
},
recover:function(obj){
objectPool.push(obj); //對象池回收dom
}
}
};
//利用objectPoolFactory來裝載一些iframe的對象:
var iframeFactory=objectPoolFactory(function(){
var iframe=document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload=function(){
iframe.onload=null; //防止iframe重複加載的bug
iframeFactory.recover(iframe); //iframe加載完成後回收節點
}
});
使用如:
for(var i=0,src,iframeN;src=['http:// baidu.com','http:// QQ.com','http:// 163.com'][i++],iframeN=i;){
var iframeN=iframeFactory.create();
iframeN.src=src;
}
對象池是另外一種性能優化方案,它跟享元模式有一些相似之處,但沒有分離內部狀態和外部狀態這個過程。本章用享元模式完成了一個文件上傳的程序,其實也可以用對象池+事件委託來代替實現。
下面該說說著名的“Ajax分頁緩存”了,近兩年這個可是個“明星人物”。它基於這樣一個背景:當你點擊某一頁時,網站會想後臺發一個請求,接收到返回數據後跳過去(局部刷新)。這時,如果沒有設置緩存,那麼原來頁面的數據就不會被瀏覽器“記住”,當你返回這個頁面時瀏覽器會再一次發送請求,甚至當你中途退出後再進來,又回到原來的下標了。這大大加重了瀏覽器和服務器的負擔!
實現【分頁緩存】的方式有不少種,這裏說比較簡單的一種:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ajax分頁緩存·試驗</title>
<style>
/* ... */
</style>
</head>
<body>
<div class="wrap flex_column">
<div class="content flex_column"></div>
<div class="page">
<ul class="flex_row">
<!-- <li></li> -->
</ul>
</div>
</div>
<script src="./ajax.js"></script>
<script>
var oContent=document.querySelector('.content');
var oPages=document.querySelector('.page ul li');
var cache={};
changePage();
setInterval(function(){
cache={};
},1000);
function changePage(){
for(let i=0,len=oPages.length;i<len;i++){ //這裏實際要換成從後端ajax過來的長度
oPages[i].onclick=function(){
var page=i+1;
if(page in cache){
addDom(cache[page]);
console.log('已經存在了數據');
}else{
goTo(page);
console.log('數據還沒有,正在加載中...');
}
console.log(cache);
}
}
}
goTo(1);
function goTo(page){
Ajax({
url:'https://route.showapi.com/181-1',
method:'GET',
data:{
showapi_appid:'30603',
showapi_sign:'98960666afeb4992ae91971d13494090',
num:8,
page:page
},
success:function(res){
var result=JSON.parse(res);
var dataList=result.showapi_res_body.newslist;
//先獲取到我們的數據數組
addDom(dataList);
cache[page]=dataList;
}
});
}
function addDom(result){
var dataList=result;
var dataLength=dataList.length;
var str='';
for(var i=0;i<dataLength;i++){
str+=`
<a href="${dataList[i].url}" class="items flex_row">
<div class="img">
<img src="xxx" alt="" />
</div>
<div class="bd">
<p class="label">${dataList[i].title}</p>
</div>
</a>`
}
oContent.innerHTML=str;
}
</script>
</body>
</html>
第二個代碼中筆者使用的ajax模塊是自己封裝的(筆者提倡能自己封裝一下,這樣感觸更深一些),已總結成文,鏈接如下:
原生JS封裝ajax方法(支持jsonp)