JavaScript 设计模式之代理模式
代理模式是为了一个对象提供一个代用品或占位符,以便控制对它的访问。
代理模式是一种有用的模式,在生活中可以找到很多代理模式的场景。
1、保护代理之不直接面对,不愿暴露,而是通过代理方式与外部接触;
2、虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才创建。
- 虚拟代理实现图片预加载
在 web 开发中,图片预加载是一种常用的技术,如果直接给某个 img 标签节点设置 src 属性,由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张 loading 图片占位,然后用异步方式加载图片,等图片加载好了再把它填充到 img 节点里,这种场景就很适合使用虚拟代理。
var showImg = (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() {
showImage.setSrc(this.src);
}
return {
setSrc: function(src) {
showImg.setSrc('loading.gif');
img.src = src;
}
}
})();
proxyImg.setSrc('thumb.png');
- 虚拟代理合并 http 请求
比如一个文件同步功能,当用户选中一个 checkbox 就代表该文件要同步到另外一台服务器上面
<input type="checkbox" id="vue.js"> vue.js
<input type="checkbox" id="react.js"> react.js
<input type="checkbox" id="angular.js"> angular.js
<input type="checkbox" id="node.js"> node.js
<input type="checkbox" id="require.js"> require.js
<input type="checkbox" id="sea.js"> sea.js
<script>
var syncFiles = 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) {
syncFiles(this.id)
}
}
}
</script>
如此这样点击就直接发送同步请求,频繁的网络请求将会带来相当大的开销。如果可以通过一个代理函数 proxySyncFiles 来收集一段时间之内的请求,最后一次性发送给服务器,这样就能大大减轻服务器的压力。
<input type="checkbox" id="vue.js"> vue.js
<input type="checkbox" id="react.js"> react.js
<input type="checkbox" id="angular.js"> angular.js
<input type="checkbox" id="node.js"> node.js
<input type="checkbox" id="require.js"> require.js
<input type="checkbox" id="sea.js"> sea.js
<script>
var syncFiles = function(id) {
console.log('开始同步文件,id 为:' + id);
}
var proxySyncFiles = (function() {
var cache = [],
timer;
return function(id) {
cache.push(id);
if (timer) {
return;
}
timer = setTimeout(function() {
syncFiles(cache.join(','));
clearTimeout(timer);
timer = null;
cache.length = 0;
}, 2000)
}
})();
var checkbox = document.getElementsByTagName('input');
for (var i = 0, c; c = checkbox[i++];) {
c.onclick = function() {
if (this.checked === true) {
proxySyncFiles (this.id)
}
}
}
</script>
- 缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前的一致,则可以直接返回前面存储的运算结果。
var mult = function() {
console.log('开始计算');
var a = 1;
for (var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
// 加入缓存代理函数
var proxyMult = (function() {
var cache = {};
return function() {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = mult.apply(this, arguments);
}
})();
console.log(proxyMult(1, 2, 3, 4)); // 24
console.log(proxyMult(1, 2, 3, 4)); // 24
- 用高阶函数动态创建代理
通过传入高阶函数这种更加灵活的方式,可以为种计算方法创建缓存代理。
var mult = function() {
console.log('开始计算');
var a = 1;
for (var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
var plus = function() {
console.log('开始计算');
var a = 0;
for (var i = 0, len = arguments.length; i < len; 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);
}
};
var proxyMult = createProxyFactory(mult);
var proxyPlus = createProxyFactory(plus);
console.log(proxyMult(1, 2, 3, 4)); // 24
console.log(proxyMult(1, 2, 3, 4)); // 24
console.log(proxyPlus(1, 2, 3, 4)); // 10
console.log(proxyPlus(1, 2, 3, 4)); // 10