定义
代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。举个生活例子,比如说你要追一个妹子A,但是你又不认识A。这时候可以从A身边的朋友发散。你认识A的闺蜜B。此时你就可以通过B去认识A,之后一切就so easy了。
从上面的例子可以看出,代理模式的关键是当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本地对象。
作用
代理模式的主要作用是想在访问一个类时做一些控制。
代理种类
按照职责划分,代理模式具体可以分为保护代理、虚拟代理、缓存代理、远程代理、防火墙代理。
还是开头的那个追妹子的例子,闺蜜B肯定会考量你的品行举止、有没车、有没房等条件,如果你样样俱全,不用考虑,直接把你介绍给A。但如果你自顾不暇、没工作、没存款、性格暴躁,那B肯定直接将你的请求忽略。这就是保护代理,B会帮A忽略掉一些请求。
经过了保护代理的考量,然后你要开始精心准备你的追求计划了。比如写一万字情书、送999朵玫瑰、送跑车等等。可见代价还是挺大的,B肯定不会让你立马就执行这些动作。B会去打听A的心情,如果A的心情好,才会让你去执行那些计划。这样成功率会比较大。这就是虚拟代理。虚拟代理把一些开销很大(写一万字情书、送999朵玫瑰、送跑车)的对象,延迟到真正需要它的时候才去创建。
本文主要讨论的就是保护代理和虚拟代理,其他可自行通过查阅资料了解。保护代理用于控制不同权限的对象对目标对象的访问;虚拟代理是最常用的一种代理模式。
举例
下面就用虚拟代理实现图片预加载功能,web开发中,如果要显示一张很大的图片,经常会使用较小的图片先占位,然后再用异步请求,等大图加载完了再显把它填充到img的src属性中。
var myImage = (function () {
var imageNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
}
}
})();
var proxyImage = (function () {
var img = new Image;
img.onload = function () {
myImage.setSrc(this.src);
}
return {
setSrc: function (src) {
myImage.setSrc('loading.gif');
img.src = src;
}
}
})();
// test
proxyImage.setSrc('largeImg.png');
上面代码实现了一个通过proxyImage间接访问myImage。在加载完大图前,用一张loading图先占位,避免出现白屏占位的情况。也许你会说,上面的例子通过给myImage加一个img.onload事件,就可以达到一样的效果。
var myImage = (function () {
var imageNode = document.createElement('img');
document.body.appendChild(imgNode);
var img = new Image;
img.onload = function () {
imgNode.src = img.src;
};
return {
setSrc: function (src) {
imgNode.src = 'loading.gif';
img.src = src;
}
}
})();
// test
myImage.setSrc('largeImage.png');
第二段代码精简了许多,但是函数里面做了两件事——给img节点设置src和预加载图片。这违反了单一职责原则。而第一段代码中,myImage和proxyImage的功能都是单一的。添加预加载功能时,我们也并没有修改myImage的代码,这符合开发-封闭原则。但我们加载小图片(不需要预加载功能)时,直接调用myImage即可。
我们还可以优化一些第一段代码,让两个函数表现的一致。
var myImage = (function () {
var imageNode = document.createElement('img');
document.body.appendChild(imgNode);
return function (src) {
imgNode.src = src;
}
})();
var proxyImage = (function () {
var img = new Image;
img.onload = function () {
myImage(this.src);
}
return function (src) {
myImage('loading.gif');
img.src = src;
}
})();
// 接口表现的一致
myImage('smallImg.png');
proxyImage('largeImg.png');
缓存代理在我们开发中也是比较常用的一个手段。为一些开销大的运算结果提供暂时的存储,下次运算时如果传递进来的参数跟之前一致,直接返回结果。
var mult = function () {
var a = 1;
for (var i = 0, l < arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
};
var plus = function () {
var a = 0;
for (var i = 0, l < arguments.length; i < l; i++) {
a = a + arguments[i];
}
return a;
};
var createProxyFactory = function (fn) {
var cache = {};
return function () {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = fn.apply(this, arguments);
}
}
// test
var proxyMult = createProxyFactory(mult),
proxyPlus = createProxyFactory(plus);
proxyMult(1, 2, 3 ,4);
proxyMult(1, 2, 3 ,4);
proxyPlus(1, 2, 3 ,4);
proxyPlus(1, 2, 3 ,4);
优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 保护代理可以控制对真实对象的使用权限。
- 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
- 缓存代理可以减少多次重复计算带来的问题。
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
- 增加对象也会带来一定的内存消耗。
小结
在Javascript中最常用的就是虚拟代理和缓存代理。虽然代理模式非常有用,但是我们写业务时,只有当我们真的发现不方便直接访问某个对象的时候,再编写代理也不迟。