代理模式
wangjing013 opened this issue · comments
出于某些原因或限制,一个对象不能够直接访问另外一个对象,需要通过第三者牵线搭桥才能访问,这种模式称之为代理模式.
代理模式在日常生活中随处可见,点外卖,委托,中介所等. 他们的关系图如下:
从上面的图来说,代理对象在用户和实际对象间起的就是桥梁的作用. 当用户发送请求时,代理对象经过一系列的处理后,再将请求转发给实际对象.
在某些情况下用户来说他并不关系他访问的是代理对象还是本体. 他只要能够完成对应的事情即可.
其次代理对象和真实对象之间是存在可替换的关系,那么它们就必须具备接口的一致性.
下面来看看代理模式在开发中常见的应用场景.
图片预加载
在现代的 Web 应用中,图片是网站组成中比较重要的元素. 例如头像、背景、宣传页等等. 当我们弱网络环境访问应用时,可以看到图片位置会出现短暂的空白,常见的做法就是提供一张占位图,等真正的图片加载完成后再去进行替换。 这种场景就很适合虚拟代理.
下面看看具体的实现:
<template>
<img ref="avatar" alt="" />
</template>
<script>
export default {
props: {
avatar: {
type: String,
default: ''
}
},
mounted() {
this.proxyImg(require('./placeholder.png'))
},
methods: {
setSrc(src) {
this.$refs.avatar.src = src
},
proxyImg(src) {
const img = new Image()
img.src = this.avatar
this.setSrc(src)
img.onload = () => {
this.setSrc(this.avatar)
}
img.onerror = (error) => {
console.log('avatar load :::error', error)
}
}
}
}
</script>
通过 proxyImg
对象控制 img 对象的展示,在图片未加载成功之前都会展示提供 placeholder.png
.
缓存代理
当我们程序涉及大量的计算的时,可以通过缓存方式来提供其计算效率. 例如:
在不使用代理之前 :
var mult = function () {
console.log("开始计算了");
var a = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
};
console.log(mult(2,3,4));
console.log(mult(2,3,4));
输出:
开始计算了
24
开始计算了
24
从结果来看,虽然输入内容一样,但是依然会执行两次重复计算. 现在通过缓存来优化一下.
var mult = function () {
console.log("开始计算了");
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 () {
const args = Array.prototype.join.call(arguments, ",");
if (args in cache) {
return cache[args];
}
return (cache[args] = mult.apply(this, arguments));
};
})();
console.log(proxyMult(2, 3, 4));
console.log(proxyMult(2, 3, 4));
输出:
开始计算了
24
24
其实这种模式,在 Vue 中 computed 属性中就有应用缓存代理.
事件代理
这应该是我们经常会遇到的一个场景,给列表中的每一项都添加一个点击事件。如下
<ul id="father">
<li>事项1</li>
<li>事项2</li>
<li>事项3</li>
<li>事项4</li>
<li>事项5</li>
<li>事项6</li>
</ul>
通常有两种做法,第一种就是每个 li
元素都绑定一个事件.
let lis = document.getElementsByTagName("li");
for (let i = 0, len = lis.length; i < len; i++) {
lis[i].addEventListener("click", function (e) {
e.preventDefault();
console.log("li=", e.target.innerHTML);
});
}
虽然上面方式达到目的,但是当随着节点增多,就会造成性能问题.
第二种利用事件冒泡,仅在父元素绑定一个事件.
let father = document.getElementById("father");
father.addEventListener("click", function (e) {
e.preventDefault();
console.log("li:", e.target.innerHTML);
});
相比前一种做法,现在只需要绑定一次而不需要去绑定 N 次 - 这种做法就是事件代理,它可以很大程度上提高我们代码的性能。
上面列举只是一些常见的使用场景,但是远远不止这些.
总结
通过定义来反向推断其使用场合. 当一个对象不能直接去访问某个对象,而需要通过引入另外一个对象(代理对象)时,那么此时就可以考虑代理模式.