# 代理模式

# 定义

代理模式(Proxy Pattern) 是指为一个原对象找一个代理对象,以便对原对象进行访问。即在访问者与目标对象之间加一层代理,通过代理做授权和控制。代理模式的英文叫做 Proxy 或 Surrogate,它是一种对象结构型模式。

最常见的例子就是经纪人代理明星业务,假设你作为投资人,想联系明星打广告,那么你就需要先经过代理经纪人,经纪人对你的资质进行考察,并为你进行排期,替明星过滤不必要的信息。

事件委托/代理、jQuery 的 $.proxy、ES6 的 proxy 都是这一模式的实现。

# 实践应用

# 事件代理的实现

事件代理,可能是代理模式最常见的一种应用方式,也是一道实打实的高频面试题。它的场景是一个父元素下有多个子元素,像这样:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>事件代理</title>
</head>
<body>
  <div id="father">
    <a href="#">链接1号</a>
    <a href="#">链接2号</a>
    <a href="#">链接3号</a>
    <a href="#">链接4号</a>
    <a href="#">链接5号</a>
    <a href="#">链接6号</a>
  </div>
</body>
</html>

我们现在的需求是,希望鼠标点击每个 a 标签,都可以弹出“我是xxx”这样的提示。比如点击第一个 a 标签,弹出“我是链接1号”这样的提示。这意味着我们至少要安装 6 个监听函数给 6 个不同的的元素(一般我们会用循环,代码如下所示),如果我们的 a 标签进一步增多,那么性能的开销会更大。

用代理模式实现多个子元素的事件监听,代码会简单很多:

// 获取父元素
const father = document.getElementById('father')

// 给父元素安装一次监听函数
father.addEventListener('click', function(e) {
    // 识别是否是目标子元素
    if(e.target.tagName === 'A') {
        // 以下是监听函数的函数体
        e.preventDefault()
        alert(`我是${e.target.innerText}`)
    }
} )

在这种做法下,我们的点击操作并不会直接触及目标子元素,而是由父元素对事件进行处理和分发、间接地将其作用于子元素,因此这种操作从模式上划分属于代理模

# 虚拟代理

懒惰加载图片的方式:先用loading图片占位,再用异步加载图片,等图片加载完毕后再将完成的图片加载到img标签中。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <style></style>
  </head>
  <body></body>
  <script>
    //定义图片节点类
    class PreLoadImage {
      constructor(name) {
        this.init(name)
      } //设置图片节点的src
      setSrc(src) {
        this.img.src = src
      } //初始化图片节点并加到dom中
      init(name) {
        this.img = new Image()
        document.body.appendChild(this.img)
        this.img.src = name
      }
    }

    // 定义代理图片类
    class ImgProxy {
      constructor(name, img) {
        //通过虚拟的图片节点加载真实图片
        this.img = new Image()
        this.img.src = name //真实图片加载完
        this.img.onload = () => {
          this.setSrc(img)
        }
      } //设置图片节点的真正src
      setSrc(img) {
        // img.setSrc(this.img.src)
        setTimeout(() => {
          preLoadImage.setSrc(this.img.src)
        }, 2000)
      }
    }
    //实例化dom图片节点,初始显示图片为init.png
    var preLoadImage = new PreLoadImage(
      'https://img-blog.csdnimg.cn/20181120093154407.gif'
    )
    //通过代理对象加载真正的图片并传入图片节点对象
    var imgProxy = new ImgProxy(
      'https://img-blog.csdnimg.cn/3a0859c1191b4138a9fa57f4204e5077.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGV6aGVuMjA=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center#pic_center',
      preLoadImage
    )
  </script>
</html>

# Vue3 的数据代理

const obj = {
  name: "JiMing",
};

const proxyObj = new Proxy(obj, {
  // target 目标对象 即 obj
  // property 被获取的属性名。
  get(target, property) {
    console.log(`访问了 obj.${property}`);
    return target[property];
  },
  // target 目标对象 即 obj
  // 将被设置的属性名
  set(target, property, value) {
    console.log(`修改了 obj.${property}`);
    target[property] = value;
  },
});

proxyObj.name; // 访问了 obj.name
proxyObj.name = "Ji"; // 修改了 obj.name