代理模式,式如其名——在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。
举个不太恰当的例子,仅供理解代理模式。八国联军侵华后,列强知道如果强制推行殖民统治,势必会造成极大反抗,有感于此,便决定扶植某些人作为自己的代理人,来镇压人民。这个可以当做是代理模式的一种应用。。。
在 ES6 中,新增了 Proxy,可以使用它来实现对某个对象的代理。基本语法如下:
const proxy = new Proxy(target, handler);
target 参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
const obj = {}
const proxy = new Proxy(obj, {
get() {
return 10
}
})
console.log(proxy.a) // 10
具体使用,请阅读阮一峰老师的 Proxy (opens new window) 。
# 事件代理
事件代理是最常见的一种代理模式,举个例子,给每个 li 标签增加点击方法,初学者可能会这么写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="box">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
const liArr = document.getElementsByTagName('li');
for (let i = 0, len = liArr.length; i < len; i++) {
console.log('lili', liArr[i])
liArr[i].onclick = function() {
console.log(liArr[i].innerHTML);
}
}
</script>
</body>
</html>
上面虽然我们成功的给每个 li 标签添加了点击事件,但是性能不好,如果 li 标签有很多,就需要循环遍历给每个 li 标签添加事件。
下面我们借助事件具有 “冒泡” 的特性,将事件监听绑定到 ul 上,这种做法就是事件代理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="box">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
box.onclick = function(e) {
const target = e.target;
if (target.nodeName.toLocaleLowerCase() == 'li') {
console.log(e.target.innerHTML)
}
}
</script>
</body>
</html>
# 虚拟代理
这里以图片预加载为例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img src="" alt="" id="img"/>
<script>
class PreLoadImage {
constructor(imgNode) {
this.imgNode = imgNode
}
setSrc(imgUrl) {
this.imgNode.src = imgUrl
}
}
class ProxyImage {
static LOADING_URL = './1.jpeg'
constructor(targetImage) {
this.targetImage = targetImage
}
setSrc(targetUrl) {
this.targetImage.setSrc(ProxyImage.LOADING_URL)
const vImage = new Image()
vImage.onload = () => {
this.targetImage.setSrc(targetUrl)
}
vImage.src = targetUrl
}
}
const proxyImg = new ProxyImage(new PreLoadImage(img))
proxyImg.setSrc('https://img1.baidu.com/it/u=2260373675,224366072&fm=26&fmt=auto&gp=0.jpg')
</script>
</body>
</html>
上述代码中 PreLoadImage 类专心做 DOM 层面的事,ProxyImage 类处理预加载相关的逻辑,通过对 ProxyImage 这个代理实现了对真实 img 节点的操作。
# 缓存代理
当我们对一些复杂的数据进行求值后,为了避免二次计算,提高性能,可能将第一次的结果进行缓存,下次取值直接从缓存中读取即可。
function addAll() {
let sum = 0
for (let i = 0, len = arguments.length; i < len; i++) {
sum += arguments[i];
}
return sum;
}
const proxyAddAll = (function() {
const resCache = {};
return function() {
const args = Array.prototype.join.call(arguments, ',');
if (args in resCache) {
return resCache[args];
}
return resCache[args] = addAll(...arguments)
}
})()
console.log(proxyAddAll(1, 2, 3, 4, 5, 6)) // 21
console.log(proxyAddAll(1, 2, 3, 4, 5, 6)) // 21
# 保护代理
举个例子,就比如你的小金库,不能是任何人都能访问的,只有极少数的人才可以访问,这个时候如果有人想访问你的小金库,需要加层校验
const you = {
money: 99999999999999
}
const proxy = new Proxy(you, {
get() {
}
})
保护代理,在 getter 和 setter 函数里去进行校验和拦截,确保一部分变量是安全的。目前我们实现保护代理的首选方案推荐 ES6 中的 Proxy 。