代理模式,式如其名——在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。

举个不太恰当的例子,仅供理解代理模式。八国联军侵华后,列强知道如果强制推行殖民统治,势必会造成极大反抗,有感于此,便决定扶植某些人作为自己的代理人,来镇压人民。这个可以当做是代理模式的一种应用。。。

在 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 。