@TOC
深入理解JavaScript设计模式之代理模式
什么是代理模式
官方语言:
为一个对象提供一个代用品或者占位符,以便控制对他的访问。
我口中的代理模式:
你想干一件事,但是你不方便亲自出马,于是你找了个"替身"帮你搞定,这个"替身"就是你的"代理",再俗一点可以理解为------"媒婆"
一个简单的代理模式(普通代理)
你大学的时候喜欢一个女孩叫做小凤,但是你害羞的连微信都不敢加,咋办?这个时候你发现小凤有个好闺蜜小饶,你就找她的闺蜜小饶帮忙:"饶姐,帮我把这朵花送给小凤吧!", 于是小饶就成了你的"代理",帮你完成了送花任务,虽然最后结果很悲剧,小凤可能记得有人给她送花,却不记得是谁送的花T.T。
这就是代理模式在生活中的真是体现,那我用代码给他描述出来:
javascript
<html>
<body>
<script>
// 引入代理 xiaoRao,即x通过 xiaoRao 来给 xiaoFeng 送花:
var Flower = function () {};
var xiaoWu = {
sendFlower: function (target) {
var flower = new Flower();
target.receiveFlower(flower);
},
};
var xiaoRao = {
receiveFlower: function (flower) {
xiaoFeng.receiveFlower(flower);
},
};
var xiaoFeng = {
receiveFlower: function (flower) {
console.log("收到花 " + flower);
},
};
xiaoWu.sendFlower(xiaoRao);
</script>
</body>
</html>

虚拟代理:等小凤心情好的时候再送花
延迟执行操作,直到真正需要时才创建实际对象。有时候你明明准备好了鲜花和情书,但是你知道小凤现在心情不好,忙着毕设论文,这时候贸然出击只会让她觉得更烦恼,所以你决定让小饶去探探风"小凤心情好不好,不好的话我一会来!",这个时候,小饶就变成了你的"虚拟代理",代表你提前与小凤建立联系,等到合适的时机,你在亲自登场,制造惊喜。代码实现为:
javascript
<html>
<body>
<script>
var Flower = function () {};
var xiaoWu = {
sendFlower: function (target) {
var flower = new Flower();
target.receiveFlower(flower);
},
};
var xiaoRao = {
receiveFlower: function (flower) {
xiaoFeng.listenGoodMood(function () {
// 监听 xiaoFeng 的好心情
xiaoFeng.receiveFlower(flower);
});
},
};
var xiaoFeng = {
receiveFlower: function (flower) {
console.log("心情好了:收到花 " + flower);
},
listenGoodMood: function (fn) {
setTimeout(function () {
// 假设 10 秒之后 xiaoFeng 的心情变好
fn();
}, 3000);
},
};
xiaoWu.sendFlower(xiaoRao);
</script>
</body>
</html>

保护代理:设置追求小凤门槛
控制对象的访问权限,过滤掉一些不符合条件的请求。 你以为谁都能追小凤吗?错!,小凤可是女神级别的存在,身高没过180
,颜值没上9
分直接pass
,于是小凤找了闺蜜小饶做"感情代理人"所有想送花的都要经过小饶的考核,长相,情商,身高等过滤。这就是典型的保护代理模式,通过中间人控制访问权限保护目标对象不被滥用。代码如下实现:
javascript
<html>
<body>
<script>
var Flower = function () {};
var xiaoWu = {
sendFlower: function (target) {
var flower = new Flower();
var height = 180,
charm = 9;
target.receiveFlower(flower, height, charm);
},
};
var xiaoRao = {
receiveFlower: function (flower, height, charm) {
if (height >= 175 && charm >= 8) {
console.log("👮♀️ 小饶审核通过!,等小凤心情好了送花!");
xiaoFeng.listenGoodMood(function () {
// 监听 xiaoFeng 的好心情
xiaoFeng.receiveFlower(flower);
});
} else {
console.log("🚫 小饶说:你不符合条件,不能送花!");
}
},
};
var xiaoFeng = {
receiveFlower: function (flower) {
console.log("心情好了:收到花 " + flower);
},
listenGoodMood: function (fn) {
setTimeout(function () {
// 假设 10 秒之后 xiaoFeng 的心情变好
fn();
}, 3000);
},
};
xiaoWu.sendFlower(xiaoRao);
</script>
</body>
</html>
如果要求没有满足小饶过滤的门槛
javascript
var height = 170,charm = 9;
target.receiveFlower(flower, height, charm);

代理再实际开发中的使用
虚拟代理实现图片预加载
在开发中,图片的预加载是一种非常常用的技术,如果直接给某个img
标签节点设置src
属性,由于图片过大或者网络不佳,图片的位置往往有段时间会一片空白造成用户空白焦虑,最常见的解决办法就是用一张loading
图片进行占位,然后用异步的方式加载图片,等图片加载好了在把他填充到img
节点里面,这种场景就很适合使用虚拟代理。 下面代码我通过setTimeout
进行一次图片加载的延迟,先显示loading
占位图片在异步渲染图片。
javascript
<html>
<body>
<script>
var myImage = (function () {
var imgNode = document.createElement("img");
imgNode.style.width = "600px";
imgNode.style.height = "400px";
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
},
};
})();
// 代理显示图片
var proxyImage = (function () {
console.log("proxyImage被执行了");
var img = new Image();
img.onload = function () {
myImage.setSrc(this.src);
};
return {
setSrc: function (src) {
myImage.setSrc("./loading.png");
setTimeout(() => {
img.src = src;
}, 1000);
},
};
})();
proxyImage.setSrc(
"https://i2.hdslb.com/bfs/archive/a193d392bbd099b15d59dee92027d28962b9b4c0.jpg"
);
</script>
</body>
</html>

虚拟代理合并http请求
开发中还有一个场景,就是很多多选框,每选中一次则调用接口上报一次,如果有100个选框,以我玩LOL的手速,一秒三次很正常,如果一秒发送三次请求,如此频繁的网络请求将会给服务器带来相当大的开销,解决办法是使用proxy收集一段时间内的请求,最后一次性发送给服务器,比如等待两秒之后才把这两秒之内需要发送请求的id发给服务器,如果不是对实时性要求非常高的系统,2秒延迟不会带来特别大的副作用,反而大大减轻服务器的压力。
没使用虚拟代理合并http请求前
javascript
<html>
<body>
<div class="box">
<label> <input type="checkbox" id="1" /> 选项1<br /></label>
<label> <input type="checkbox" id="2" /> 选项2<br /></label>
<label> <input type="checkbox" id="3" /> 选项3<br /></label>
<label> <input type="checkbox" id="4" /> 选项4<br /></label>
<label> <input type="checkbox" id="5" /> 选项5<br /></label>
<label> <input type="checkbox" id="6" /> 选项6<br /></label>
<label> <input type="checkbox" id="7" /> 选项7<br /></label>
<label> <input type="checkbox" id="8" /> 选项8<br /></label>
<label> <input type="checkbox" id="9" /> 选项9<br /></label>
<label> <input type="checkbox" id="10" /> 选项10<br /></label>
</div>
<script>
const inputs = document.querySelectorAll('input[type="checkbox"]');
const box = document.querySelector(".box");
const upload = (id) => {
console.log("上传文件id:", id);
};
box.addEventListener("click", (e) => {
let target = e.target;
let { nodeName, checked, id } = target;
if (nodeName === "INPUT") {
if (checked) {
upload(id);
}
}
});
</script>
</body>
</html>

没使用虚拟代理合并http请求后:
javascript
<html>
<body>
<div class="box">
<label> <input type="checkbox" id="1" /> 选项1<br /></label>
<label> <input type="checkbox" id="2" /> 选项2<br /></label>
<label> <input type="checkbox" id="3" /> 选项3<br /></label>
<label> <input type="checkbox" id="4" /> 选项4<br /></label>
<label> <input type="checkbox" id="5" /> 选项5<br /></label>
<label> <input type="checkbox" id="6" /> 选项6<br /></label>
<label> <input type="checkbox" id="7" /> 选项7<br /></label>
<label> <input type="checkbox" id="8" /> 选项8<br /></label>
<label> <input type="checkbox" id="9" /> 选项9<br /></label>
<label> <input type="checkbox" id="10" /> 选项10<br /></label>
</div>
<script>
const inputs = document.querySelectorAll('input[type="checkbox"]');
const box = document.querySelector(".box");
const upload = (id) => {
console.log("上传文件id:", id);
};
box.addEventListener("click", (e) => {
let target = e.target;
let { nodeName, checked, id } = target;
if (nodeName === "INPUT") {
if (checked) {
proxy(id);
}
}
});
const proxy = (function () {
let cache = [];
let timer;
return function (id) {
cache.push(id);
if (timer) {
return;
}
timer = setTimeout(() => {
upload(cache.join(","));
clearTimeout(timer);
timer = null;
cache.length = 0;
}, 2000);
};
})();
</script>
</body>
</html>

缓存代理求乘积【单一职责原则】
一些开销大的运算结果提供暂时的存储,下次运算的时候,如果传递进来的参数跟之前的一致,可以直接返回前面存储的运算结果。
javascript
<html>
<body>
<script>
var mult = function () {
console.log("我是mult被执行了");
var a = 1;
for (var i = 0, l = arguments.length; i < l; 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):", proxyMult(1, 2)); // 输出:2 执行mult
console.log("proxyMult(1, 2, 3):", proxyMult(1, 2, 3)); // 输出:26 // 执行mult
console.log("proxyMult(1, 2, 3, 4):", proxyMult(1, 2, 3, 4)); // 输出:24 // 执行mult
console.log("proxyMult(1, 2, 3, 4):", proxyMult(1, 2, 3, 4)); // 输出:24 // 不执行mult
</script>
</body>
</html>

高阶函数动态创建缓存代理【单一职责原则】
通过传入高阶函数这种更加灵活的方式,可以为各种计算方法创建缓存代理。现在这些计算方法被当作参数传入一个专门用于创建缓存代理的工厂中, 这样一来,我们就可以为乘法、加法、减法等创建缓存代理,代码如下:
javascript
<html>
<body>
<script>
/**************** 计算乘积 *****************/
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));
};
};
var proxyMult = createProxyFactory(mult),proxyPlus = createProxyFactory(plus);
console.log(proxyMult(1, 2, 3, 4)); // 输出:24 执行mult
console.log(proxyMult(1, 2, 3, 4)); // 输出:24 缓存中取出
console.log(proxyPlus(1, 2, 3, 4)); // 输出:10 执行plus
console.log(proxyPlus(1, 2, 3, 4)); // 输出:10 缓存中取出
</script>
</body>
</html>

总结
设计模式不是"炫技",而是"沉淀",希望通过阅读和学习《JavaScript设计模式》和实践中,在显示业务需求开发中写出更具有可维护性,可扩展性的代码。
致敬------ 《JavaScript设计模式》· 曾探