# 代理模式

代理模式是一个对象提供一个代用品或占位符,以便控制对它的访问。

当不方便直接访问目标对象或访问目标对象时需要额外操作的时候,为了尽可能遵守单一指责原则和开放—封闭原则,可以使用代理模式对代码结果进行优化,使得目标对象高内聚,低耦合,易于扩展和复用。

代理模式包含了许多变体,在 JavaScript 开发中最常用的是虚拟代理和缓存代理。

# 虚拟代理

对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象。

这里以实现图片预加载为例子,myImg 对象作为真实对象,用于创建 img 标签,并对外提供 setSrc 接口。virtualProxyImg 作为虚拟代理对象,用以控制对 myImg 的访问,在真正的图片加载完成之前,先将 img 标签的 src 属性设置为一张本地的图片。

const myImg = (function() {
  const img = document.createElement('img')
  document.body.appendChild(img)
  return {
    setSrc(src) {
      img.src = src
    }
  }
})()

const virtualProxyImg = (function() {
  const img = new Image()
  img.onload = function() {
    // 当图片加载完成时,通知 myImg 替换 src
    myImg.setSrc(this.src)
  }

  return {
    setSrc(src) {
      myImg.setSrc('loading.gif') // 图片加载完成之前使用另外一张图片进行占位
      img.src = src
    }
  }
})()

virtualProxyImg.setSrc('https://cdn.striveforus.com/IMG_7151.JPG')

img 标签设置 src 属性和图片预加载这两个功能被隔离在两个对象里,它们可以各自变化而不互相影响,这是符合开放—封闭原则的。

# 缓存代理

缓存代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而可以避免某些方法的重复执行,优化系统性能。

这里以对乘法运算创建缓存代理为例:

const multiplication = function(...params) {
  console.log('multiplication')
  let result = 1
  for (let i = 0; i < params.length; i++) {
    result *= params[i]
  }
  return result
}

const cacheProxyMultiplication = (function() {
  const cache = {} // 缓存对象

  return function() {
    const params = Array.from(arguments).join(',')
    if (params in cache) {
      // 如果有缓存
      return cache[params]
    }
    return (cache[params] = multiplication.apply(this, arguments))
  }
})()

console.log(cacheProxyMultiplication(7, 11))
console.log(cacheProxyMultiplication(7, 11))
// multiplication
// 77
// 77

多次调用 cacheProxyMultiplication() 函数时,如果再次传入相同参数,将不会重复执行 multiplication() 函数,而是返回缓存对象 cache 保存的结果。