代理模式就是 A 要对 C 做一件事,但是他可能不方便直接对 C 做这件事,A 就拜托 B,让 B 帮 A 做这件事,这就是代理。
书中举了一个小明想送花给 A,但是又不好意思,而且不好估计 A 什么时候心情好(心情好送花更能得到好感)才送花,于是小明拜托 A 的闺蜜 B,让 B 帮小明送花。 B 因为熟悉 A,所以可以在检测到 A 心情好的时候将花交给 A:
var Flower = function(){};
var xiaoming = {
sendFlower: function( target){
var flower = new Flower();
target.receiveFlower( flower );
}
};
var B = {
receiveFlower: function( flower ){
A.listenGoodMood(function(){ // 监听A 的好心情
A.receiveFlower( flower );
});
}
};
var A = {
receiveFlower: function( flower ){
console.log( '收到花 ' + flower );
},
listenGoodMood: function( fn ){
setTimeout(function(){ // 假设10 秒之后A 的心情变好
fn();
}, 10000 );
}
};
xiaoming.sendFlower( B );
保护代理和虚拟代理
保护代理类似过滤器/拦截器一样的功能,就好像 B 可以帮 A 过滤掉一些不合理的送花请求,比如送花的人年纪太大或不合适,B可以不接受这个请求。
另外,假设现实中花的价格不菲,导致在程序世界里,每实例化一朵花都是很大的开销,那么我们可以把new Flower()
放在代理 B 中进行,代理 B 会在 A 心情好的时候再实例出花送给 A 。这就是代理模式的另一种模式:虚拟代理。虚拟代理把一些
开销很大的操作延迟到真正需要它的时候才创建。(怎么听着像“惰性代理”更合适):
var B = {
receiveFlower: function(flower) {
A.listenGoodMood(function() {
var flower = new Flower();
A.receiveFlower(flower);
})
}
}
虚拟代理实现图片预加载
var myImage = (function() {
var imgNode = 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('./image/Loading_icon.gif');
img.src = src;
}
}
})()
proxyImage.setSrc('http://qnimate.com/wp-content/uploads/2014/03/images2.jpg');
代码的运行流程是:
-
myImage
立即调用,只做了一件事:把一个空的<img>
元素插入 html 中,并返回一个对象,对象的setSrc
方法可以把图片的 src 修改为传入的参数。 -
proxyImage
立即调用,做了两件事:实例化出一个img
对象,绑定 img 的onload
对象,当图片加载完成后,给图片设置新的src
。 -
pxoxyImage
的setSrc
做了两件事:调用myImage.setSrc
设置空img
元素的src
为一张本地图片;给img
的src
赋值为传入的图片地址。 -
onload
事件触发,myImage.setSrc(this.src);
再次被执行,<img>
的src被替换。
img 的
onload
的执行时机:看代码的时候对程序的运行有点疑惑,原因是不清楚onload
的执行时机,一番实验,发现在 Chrome 下,只要Image
实例出来的img
的src
属性被赋值并且图片加载完成,onload
就会触发。Image
实例出来的对象作用可能就在于结合onload
事件,对img的src
进行赋值的时候可以给 html 上的<img>
元素执行一些操作。这说明我之前老想着的“Image
实例出来的img
难道要插入 html 里?”的想法是有问题的。
虚拟代理合并 HTTP 请求
考虑如下场景:有一个有若干 checkbox 表单项的表单,每选中一个 checkbox 都向服务器发送一次请求。如果用户以极高的频率不断选中 checkbox 的话,就会产生大量的 HTTP 请求,给服务器增加负担:
// html
<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
<input type="checkbox" id="5"></input>5
<input type="checkbox" id="6"></input>6
<input type="checkbox" id="7"></input>7
<input type="checkbox" id="8"></input>8
<input type="checkbox" id="9"></input>9
</body>
// js
var synchronousFile = function (id) {
console.log('开始同步文件,id 为: ' + id);
};
var checkbox = document.getElementsByTagName('input');
for (var i = 0, c; c = checkbox[i++];) {
c.onclick = function () {
if (this.checked === true) {
synchronousFile(this.id);
}
}
};
这个时候增加一个代理函数,对请求函数synchronousFile()
做封装,规定每两秒内的请求放在一个 HTTP 请求中。做一个节流:
var synchronousFile = function (id) {
console.log('开始同步文件,id 为: ' + id);
};
var proxySynchronousFile = (function () {
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function (id) {
cache.push(id);
if (timer) {
return;
}
timer = setTimeout(() => {
synchronousFile(cache.join(','));
clearTimeout(timer);
timer = null;
cache.length = 0;
}, 2000);
}
})();
var checbox = document.getElementsByTagName('input');
for (var i = 0, c; c = checbox[i++];) {
c.onclick = function() {
if (this.checked === true) {
proxySynchronousFile(this.id);
}
}
}
发现一个问题,书里有很多的立即执行函数,但是在我的开发中很少意识到运用 IIFE 去编写函数。IIFE 可以产生闭包,使用场景就是:当一个函数需要被反复执行多次,但是每次执行的结果要叠加在上N次的执行结果上的时候。可能当我需要这个场景的时候,我都是把需要保存的结果放在了全局中,从而实现闭包可以实现的效果。
缓存代理
缓存代理可以为一些开销大的运行结果提供暂时的储存,在下次计算时如果传递的参数跟之前一致,可以直接返回前面存储的运算结果。
以一个计算乘积的函数为例子:
var mult = function() {
console.log('开始计算乘积');
var a = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
}
mult(2, 3);
mult(2, 3, 4);
var proxyMult = function() {
const cache = {};
return function() {
const args = Array.prototype.join(',').call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = mult.apply(this, arguments);
}
}();
proxyMult(1, 2, 3, 4) // 输出:24
proxyMult(1, 2, 3, 4) // 输出:24
这个方法虽然还有点问题,第一是防止缓存的结果过多,第二是他将传入的参数转为字符串后保存在对象中,如果传入的参数相同,但是顺序换了,那么cache
就要保存多个相同参数的计算结果了,有个方法是先将传入的参数排序,然后再存入cache
中。
代理模式的应用场景:
- 计算结算可以被缓存再利用时,将结果缓存在代理函数中
- 是用户操作但是不需要马上执行时,把执行队列缓存在代理函数中,真正需要用到 时候再依次执行
网友评论