要说谁在网站、web APP等前端应用中起到越来越重要的作用,那绝对很多人第一时间想到的就是:【缓存】!

面对越来越要求高质量、快应用的前端,缓存越来越成为当之无愧的“无冕之王”!与其相关的,一直活跃在前端“视野”中的有两个非常重要的应用:

  1. 【享元模式】中的对象池
  2. 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)​