小明要送花给小红,怕小红拒绝,小明把花交给小灯泡,小灯泡在小红心情好的时候将花送给小红,小灯泡就是代理。
保护代理
送花的人很多,小灯泡帮助小红过滤掉一些人,比如没有宝马的、年龄过大的。
小红可以保持女神形象不会直接拒绝任何人,小灯泡唱黑脸拒绝小红看不上的人。
虚拟代理
假设现实中的花价格不菲,导致在程序世界里,new Flower也是一个代价昂贵的操作。
那么我们可以把new Flower的操作交给代理小灯泡去执行,小灯泡会选择在小红心情好时再执行new Flower。
这是代理模式的另一种形式,叫作虚拟代理。
虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建
图片预加载
var myImg = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
}
};
})();
myImg.setSrc('https://img0.baidu.com/it/u=2237039644,3735368368&fm=26&fmt=auto');
上面的代码在图片被加载好之前,页面中有一段长长的空白时间。
引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中将出现一张占位的菊花图来提示用户图片正在加载。
var myImg = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
}
};
})();
// 代理对象
var proxyImg = (function () {
var img = new Image();
img.onload = function () {
myImg.setSrc(this.src);
};
return {
setSrc: async function (src) {
myImg.setSrc('http://topurl.cn/98p'); // 本地菊花图
img.src = await src();
}
};
})();
// 3秒后返回url,模拟图片加载时间
var imgUrl = function () {
return new Promise(resolve => {
setTimeout(() => {
resolve('http://topurl.cn/98q');
}, 3000);
});
};
proxyImg.setSrc(imgUrl);
代理和本体接口的一致性
如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请求本体。
其中关键是代理对象和本体都对外提供了setSrc方法,在客户看来,代理对象和本体是一致的,代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别,这样做有两个好处。
-
用户可以放心地请求代理,他只关心是否能得到想要的结果。
-
在任何使用本体的地方都可以替换成使用代理。
var myImg = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return function (src) { // 返回函数,二位对象的方法,myImg成为可执行的函数
imgNode.src = src;
};
})();
// 代理对象
var proxyImg = (function () {
var img = new Image();
img.onload = function () {
myImg(this.src);
};
return async function (src) { // 返回函数,二位对象的方法,proxyImg成为可执行的函数
myImg('http://topurl.cn/98p'); // 本地菊花图
img.src = await src();
};
})();
// 3秒后返回url,模拟图片加载时间
var imgUrl = function () {
return new Promise(resolve => {
setTimeout(() => {
resolve('http://topurl.cn/98q');
}, 3000);
});
};
proxyImg(imgUrl);
虚拟代理合并HTTP请求
假设页面中有100个checkbox,当选中时会发送请求。
如果选中一个就发送请求很占用服务器资源。
可以将2秒内选中项一起发送服务器,减少请求次数。
可以给“发送请求到服务器”做一个代理,“代理请求”的内容就是延迟2秒,将期间选中项一起给“发送请求到服务器”
var synchronousFile = function (id) {
console.log('开始同步文件,id为:' + id);
};
// 代理
var proxySynchronousFile = (function () {
var cache = [];
var timer = null;
return function (id) {
cache.push(id);
if (timer) return;
timer = setTimeout(function () {
// 2秒后调用本体发送要同步的id
synchronousFile(cache.join(','));
cache.length = 0; // 清空缓存数组
clearTimeout(timer); // 清空定时器
timer = null;
}, 2000);
};
})();
var checkbox = document.getElementsByTagName('input');
Array.prototype.forEach.call(checkbox, function (item) {
item.onclick = function () {
if (this.checked === true) {
proxySynchronousFile(this.id);
}
};
});
缓存代理
// 计算乘积,假装这个是非常好资源的操作
function mult() {
return Array.prototype.reduce.call(
arguments,
(acc, cur) => {
return acc * cur;
},
1
);
}
/**
* 代理函数
* 每次调用mult之前,查看缓存里有没有
* 有则返回缓存,没有则调用mult方法,并保存到缓存里
*/
var proxyMult = (function () {
var cache = {};
return function () {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
console.log('返回缓存');
return cache[args];
}
return (cache[args] = mult.apply(this, arguments));
};
})();
console.log(proxyMult(2, 3, 4));
console.log(proxyMult(2, 3, 4));
console.log(proxyMult(2, 3, 4));
高阶函数动态创建代理
// 计算乘积,假装这个是非常好资源的操作
function mult() {
return Array.prototype.reduce.call(
arguments,
(acc, cur) => {
return acc * cur;
},
1
);
}
// 计算加和
function plus() {
return Array.prototype.reduce.call(
arguments,
(acc, cur) => {
return acc + cur;
},
1
);
}
function proxyFactory(fn) {
var cache = {};
return function () {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
console.log('返回缓存');
return cache[args];
}
return (cache[args] = fn.apply(this, arguments));
};
}
var proxyMult = proxyFactory(mult);
var proxyPlus = proxyFactory(plus);
console.log(proxyMult(2, 3, 4));
console.log(proxyMult(2, 3, 4)); // 返回缓存
console.log(proxyPlus(2, 3, 4));
console.log(proxyPlus(2, 3, 4)); // 返回缓存