代理模式定义
代理模式是指当两个对象之间不能直接引用时,需要通过代理对象在这两个对象间起到中介的作用。代理可以代替其本体被实例化,并且使其可被远程访问。它还可以把本体实例化推迟到真正需要的时候,对于实例化比较费时的本体,或者尺寸较大处理起来耗时的类都可以使用代理。
代理模式包含虚拟代理、远程代理和保护代理。
代理类型 | 说明 |
---|---|
虚拟代理 | 虚拟代理是指用来代替巨大对象,确保它在需要的时候才被创建。在JavaScript中应用较多。 |
保护代理 | 保护代理常用于根据客户身份控制对特定方法的访问。在JavaScript中不易实现因为我们无法判断谁访问了某个对象 |
远程代理 | 在Java中,远程代理负责与远程JVM通信,以实现本地调用者与远程被调用者之间的正常交互。但在JavaScript中更多的是应用于控制对其他语言中的本体的访问。 |
代理模式应用
跨域代理
在JavaScript中,出于对安全访问因素的考虑,不允许跨域通信。跨域代理是远程代理的一种应用。
第一种代理对象形式是通过img之类的标签通过src属性可以向其他域下的服务器发送请求。不过这类get请求是单向的,不会有数据响应,一般会用于页面的埋点统计。
var imgSend = function (params) {
var img = new Image();
img.src = URL + this.args;
img.style.height = "0px";
img.style.width = "0px";
img.style.display = "none";
doc.body.appendChild(img);
img.onerror = function () {
doc.body.removeChild(img);
};
img.onload = function () {
doc.body.removeChild(img);
};
}
第二种代理对象就是通过JSONP方案来解决问题。
function jsonpFun(URL,callbackname,callback){
window[callbackname] = callback;
var oscript = document.createElement("script");
oscript.src = URL;
oscript.type = "text/javascript";
document.head.appendChild(oscript);
document.head.removeChild(oscript);
}
图片预加载
在web开发中,图片预加载时直接给img标签设置src属性,如果图片过大或者网络不佳会导致有段时间是空白的,常见的做法是先用一张loading图占位,等图片加载好了再填充到img节点。这个场景就可以通过虚拟代理来实现。
首先,先实现一个创建img并赋值src的普通对象作为本体
var createImg = (function(){
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc:function(src) {
imgNode.src = src;
}
}
})();
然后,引入一个代理对象,通过这个代理对象,在图片加载完成之前先展示loading图。
var proxyImg = (function(){
var img = new Image();
img.onload = function() {
createImg.setSrc('xxxx');
}
return {
setSrc:function(src) {
createImg.setSrc('loading');
img.src = src;
}
}
})();
//使用
proxyImg.setSrc('xxxx');
在这里使用代理类的好处就是避免一个对象承担过多的职责,当发生变化时,有可能会影响其他职责的实现。把图片请求和预加载图片这两个功能隔离在两个对象里面,可以降低耦合性,即使它们各自有变化也不会影响到对方。
同时,上例中我们保持普通本体和代理类接口一致有两个好处:客户可以放心使用代理类只关心能否获取到想要的结果;而且在任何本体的地方都可以替换成代理类。
防抖代理
<body>
<input type="checkbox" id="1"></input>1
<input type="checkbox" id="2"></input>2
<input type="checkbox" id="3"></input>3
<input type="checkbox" id="4"></input>4
<script>
var submit = function(id) {console.log("提交数据,id:" + id);}
var checkbox = document.getElementsByTagName('input');
for(var i = 0;i<checkbox.length;i++) {
checkbox[i].onclick = function(e) {
if(e.checked) {submit(this.id);}
}
}
</script>
</body>
上面实现了一个简单的场景,页面上有多个checkbox,当用户点击checkbox选中的时候会发送以数据到服务端。这段代码存在的问题是,如果短时间内用户多次操作checkbox就会出现繁忙的网络请求影响性能。
如果我们新增一个代理类,这个代理类负责收集一段时间内用户操作然后一次性发送给服务端,这样就可以减轻服务端的压力。
var proxySubmit = (function(){
//定义一个cache数组收集一段时间内需要提交的ID
var cache = [],timer;
return function(id) {
cache.push(id);
// 如果定时器存在就返回,保证不会覆盖已启动的定时器
if(timer) {return;}
timer = setTimeout(function(){
submit(cache.join(","));
clearTimeout(timer);
timer = null;
cache.length = 0;
},2000);
}
})();
缓存代理
缓存代理可以为开销大的计算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的结果。
以计算乘积为例:
let m = function (n) {
if (n<=1) {
return 1;
} else {
return n*m(n-1);
}
}
let sum=(function () {
let cache={};
return function sum(n) {
let result=0;
for (let i=1;i<=n;i++){
let r=cache[i];
if (r) {
result+=r;
} else {
r=m(i);
cache[i]=r;
result+=r;
}
}
return result;
}
})()
分页请求也可以运用代理模式,每次请求新的页码时去服务端请求数据并且缓存数据,这样下次再请求同一个页面时就可以直接使用之前的数据。