田淮仁是来自华科冰岩前端组的老油条,爱好看书和钻研技术。他将为我们带来一个js设计模式系列,分享自己的学习心得。本文是该系列的第一篇。猫友会将持续提供各种优质内容,敬请大家关注。
What’s the proxy pattern?
代理模式其实就是将违反单一性原则的类给抽离出来,尽量满足开放和封闭的原则。 相当于一个类的行为只是一种,但是你可以给这个类添加额外的行为。比如: 一个工厂制造伞,你可以给这个工厂设置一个代理,提供订单,运货,淘宝网店等多种行为。当然,里面还有最关键的一点就是,这个代理能把一些骗纸和忽悠都过滤掉,将最真实最直接的订单给工厂,让工厂能够放心的让工人加班,顺利的发年终奖.
!!!说人话:
就是将你的本体请求,放在代理的怀抱里,让他来帮你挡风挡雨。
图片懒加载
图片懒加载是指,图片的加载应该在页面全部加载完后,再去加载。这样能够提高加载的速度和网页的体验性。实际操作就是,将原来网页的src独立出来,将原来图片使用一个loading.gif代替,然后在js里面手动创建个img去加载这个src,当加载完后把src替换就可以了。
没有使用代理模式时
var delayload = (function(){
var img = document.querySelector("#img");
img.src = "loading.gif";
var newImg = document.createElement("img");
newImg.onload = function(){
img.src = newImg.src;
}
return function(src){
newImg.src = src;
}
})();
window.onload = ()=>{delayload("jimmy.jpg")};
这样写,我给满分(在没有产经这个生物的前提下).
现在,假设有一个初级产经(还没有进化那种)
她有一个需求: 我不要你的这个loading.gif图片,你换一个。
恩,好没问题。 (不过就是换一下src而已)
她又有一个需求: 你弄一个纯色的背景吧~.
MD~ 搞什么,你这样我很为难诶,我逻辑都变了,你让我怎么改。。
在没有使用代理模式的情况下,你的改动应该是:
var delayload = (function(){
var img = document.querySelector("#img");
img.style.backgoundColor="red";
var newImg = document.createElement("img");
newImg.onload = function(){
img.src = newImg.src;
}
return function(src){
newImg.src = src;
}
})();
这就直接改动代码,破坏了开放封闭原则,添加功能时,下下策就是改动函数内部代码。
所以,为了应对产经这个神奇的物种,以及能写出一手好代码,推荐使用代理模式这个利器.
来,上利器
//将背景图设置,和图片加载的src修改分开
var delayload = (function(){
var img = document.querySelector("#img");
return {
setSrc:function(src){
img.src = src;
}
}
})();
var proxy = (function(){
var img = document.createElement('img');
delayload.setSrc("loading.gif");
img.onload = function(){
delayload.setSrc(img.src);
}
return {
setSrc:function(src){
img.src = src;
}
}
})();
proxy.setSrc("jimmy.jpg");
恩,如果产经要求换图片,好解决,换一下loading.gif就over了.如果产经要求换成纯色的。也行。 再加一个方法.
var delayload = (function(){
var img = document.querySelector("#img");
return {
setSrc:function(src){
img.src = src;
},
setBg:function(color){
img.style.backgroundColor = color;
}
}
})();
var proxy = (function(){
var img = document.createElement('img');
delayload.setBg("red"); //只需要替换这一句
img.onload = function(){
delayload.setSrc(img.src);
}
return {
setSrc:function(src){
img.src = src;
}
}
})();
proxy.setSrc("jimmy.jpg");
使用代理就可以在,最大限度降低代码改动的情况下,完成需求。
当然,
如果有一天神奇生物又提出一个需求:
亲,我们现在宽带升级了,有100M了诶,你可以把加载去掉了吧,直接让用户一下就用看见图片了哦。
(呵呵~我丢你雷姆)
没事,谁叫我有代理模式嘞。改~
神奇的事发生了.
var proxy = (function(){
var img = document.createElement('img');
//删除~
img.onload = function(){
delayload.setSrc(img.src);
}
return {
setSrc:function(src){
img.src = src;
}
}
})();
proxy.setSrc("jimmy.jpg");
仔细观察就可以发现,我接口没改,对象没动,只是将delayload.setBg(“red”)删除了。
加载功能还在,只是我使用代理传了一层。66666
代理的一致性
通常来说,代理其实就是本体的一个影子,我有的行为你应该都有,我能sit,代理也能sit. 所以为了不让用于迷惑,这里有一个需求就是,代理的接口名应该和本体的接口名一致,就和上面的setSrc一样.
但通常这是个主要是针对java的静态语言的要求。因为在js里面没有实现接口的继承,而且在js里面,函数是名副其实的一等公民,所以这里对于代理的一直性要求就没有这么高了。
上面其实可以改写为:
var setImg = (function(){
var img = document.querySelector("#img");
return function(src){
img.src = src;
}
})();
var proxyImg = (function(){
var newImg = document.createElement("img");
setImg("loading.gif");
newImg.onload = function(){
setImg(newImg.src);
}
return function(src){
newImg.src = "jimmy.jpg";
}
});
proxyImg("test.jpg");
但本人推荐上面那种使用接口的形式,这样拓展性比较强,而且易于复用.
保护代理和虚拟代理
这其实很好理解,保护代理就是起到保护作用,用来过滤掉一下不必要的请求,将真正需要的递给本体。
虚拟代理和函数节流的思想是一样的,将用户对性能的rape的伤害降低到最低。就像送快递一样,一件一件的送,这个公司是不是傻~ 所以为了生存,快递公司会当物品积攒到一定程度后才会让快递哥骑着小电驴穿梭在神州大地上。
保护代理
//譬如,验证用户名是否唯一
//这里我们应用,保护代理的思想,如果用户名是不合法的,则不会将该请求给本体执行
var checkUser = function(name){
$.ajax({
url:"xxxxx",
type:"POST",
contentType:"application/json",
data:JSON.stringify({
name:name //用户名
})
})
}
var proxy = (function(){
var user = document.querySelector("#username");
return function(){
var userName = user.value;
var errMsg = detect(userName,["NotEmpty","isUserName"]); //利用策略模式,验证.
if(errMsg){
console.log(errMsg);
return;
}
checkUser(userName);
}
})();
可以清楚的看到,如果你的用户名格式不正确,这个请求是不会达到本体的。直接会在proxy里面被拦截掉。所以一个很好的代理(保护代理),能让你的请求100%用在刀刃上。
虚拟代理
上面的保护代理阐述了怎样去拒绝请求,而虚拟代理的原则是收集请求(来者不拒). 他的出发点和保护代理的是一样的,都是为了节省请求的开支。
比如: 一个在线的编辑器,他是怎样同步你的内容呢?不会是,你内容一改变就发送一起请求同步吧。这个想法显然不切实际。如果这样,我每天没事都会打开这个编辑器,把asfdsafdsafsad…在里面敲上几分钟。保证分分钟弄死他的服务器。所以一般,我们会使用虚拟代理来接受你的请求。
var send = function(article) {
return $.ajax({
url: xxx,
type: "POST",
contentType: "text/plain",
data: article
})
}
var proxy = (function() {
var content = document.querySelector('#article'),
timer;
return function() {
var article = content.value;
if (timer) { //不覆盖已经发送的请求
return;
}
timer = setTimeout(function() {
send(article)
.then(function() { //执行完成再处理
clearTimeout(timer);
timer = null;
})
}, 2000);
}
})();
setTimeout(function(){
proxy();
},2000); //定时发送请求
就算你手速再快,我就只能2s发一次。 但这只是一个比较简单的实例。如果大家感兴趣可以研究一下,作业部落这个编辑器,他这个markdown同步和体验交互式我迄今为止用过应该算最好的一个了吧~
缓存代理
这个应该是应用最多的一个代理方式了,这种方式能够真正解决运算时间问题,网络不畅问题,离线问题。
搬一个例子过来吧。
计算乘积问题
function fb(num){ //斐波那契函数,超级耗内存
if(num<=1){ 24="" ="" return="" 1;="" }="" num*fb(--num)="" }="" 缓存代理出场了="" var="" cproxy="(function(){" var="" cache="{};" function(num){="" if(cache[num]){="" console.log(`this="" is="" ${cache[num]}`);="" cache[num];="" cache[num]="fb(num);" })();="" 测试="" console.log(cproxy(4));="" cproxy(4);="" "this="" 24"<="" code="">
恩,完美。 我们知道阶乘是比较累的一个计算方法,如果某天你的leader需要你计算很多次fb(2000)。用户的电脑也吃不消啊,所以为了体验,缓存代理是你百分百女友,好好珍惜。
当然,缓存代理并不只有这一点用途,比如需要重复获取网页中大部分数据的时候,就可以考虑使用缓存代理。前端工作者,99.9999%的应该都会遇见分页的问题(请不要告诉我你的分页是同步方式). 当我们点击一个页面的时候,获取后台的数据,然后再由我恩渲染到页面上,这是这样一个流程。 首先,渲染流程可以复用,那数据的复用性该怎么做呢?
恩,猜到了吧,就是使用cache进行一个缓存,然后如果下次获取分页的页码一致的话,就可以直接使用该数据了。
//向后台发请求,获取当前页面的数据
// http.getPage(page);
var pageProxy = (function(){
var cache = {};
return function(fn){ //fn作为处理页码数据的函数
var pageData = cache[page];
if(pageData){
return fn(pageData); //返回制定页码的数据
}
http.getPage(page) //获取制定页码的数据
.then((data)=>{
cache[page] = data; //存放数据
fn(data);
})
}
})();
最后再说一句吧,由于代理写起来需要更多的逻辑和代码,如果你的产经没有什么需求的话,不用代理也是行得通的。还有就是,用不用代理和你原来的本体执行的业务逻辑是完全分开的,即,如果后期产经有什么需求,或者你对自己的代码不满意,重构的时候,再添加代理,这种方式也是很推荐的。
猫友会其事
本无心插柳, 却慢慢有一定影响力,猫友会是一个邀请制的互联网优秀人才的交流平台, 以鄂籍或在鄂求学过的同学们为主。 目前在推进的一些事情:
猫友会大讲坛,分享产品/技术/创业心得
猫友信息平台,对接武汉最优秀的团队以及愿意回武汉的优秀人才。
猫友公众号,优质原创产品/技术文章
猫友读书会,阅读+思考+总结,筹备中