前端设计模式之代理模式(九)
- 针对对象设置代理访问,不得直接访问
- 直接访问对象,在远程面向对象系统中,可能对象创建开销很大,或者某些操作需要安全控制,我们添加一个访问层访问该对象
- 例如,你通过房产中介买房子,中介就是一个代理,而非真正的房主
- 再例如,明星都有经纪人,经纪人就是一个代理

实现代理模式

class RealImg {
  fileName: string;
  constructor(fileName: string) {
    this.fileName = fileName;
    this.loadFromDist();
  }
  display() {
    console.log("display...", this.fileName);
  }
  private loadFromDist() {
    console.log("loading...", this.fileName);
  }
}
class ProxyImg {
  readImg: RealImg;
  constructor(fileName: string) {
    this.readImg = new RealImg(fileName);
  }
  display() {
    this.readImg.display();
  }
}
const proxImg = new ProxyImg("xxx.png"); // 使用代理
proxImg.display();
是否符合设计原则?
- 代理和目标分离,解耦
- 代理可自行扩展逻辑
- 目标也可自行扩展逻辑
符合开放封闭原则
场景
DOM 事件代理
<div id="div1">
  <a href="#">a1</a>
  <a href="#">a2</a>
  <a href="#">a3</a>
  <a href="#">a4</a>
</div>
<script>
  document.getElementById("div1").addEventListener("click", function (e) {
    const target = e.target;
    if (target.nodeName === "A") {
      alert(target.innerHTML);
    }
  });
</script>
虚拟代理
- virtualImage 代替真实 DOM 像图片发送了请求,却未曾渲染
class PreLoadImage {
  constructor(imgNode) {
    this.imgNode = imgNode;
  }
  setSrc(imgUrl) {
    this.imgNode.src = imgUrl;
  }
}
class ProxyImage {
  // 占位图的url地址
  static LOADING_URL = "xxxxxx";
  constructor(targetImage) {
    this.targetImage = targetImage;
  }
  // 该方法主要操作虚拟Image,完成加载
  setSrc(targetUrl) {
    // 真实img节点初始化时展示的是一个占位图
    this.targetImage.setSrc(ProxyImage.LOADING_URL);
    // 创建一个帮我们加载图片的虚拟Image实例
    const virtualImage = new Image();
    // 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
    virtualImage.onload = () => {
      this.targetImage.setSrc(targetUrl);
    };
    // 设置src属性,虚拟Image实例开始加载图片
    virtualImage.src = targetUrl;
  }
}
缓存代理
- 空间换时间
- 通过代理计算的同时对计算结果进行缓存
// addAll方法会对你传入的所有参数做求和操作
const addAll = function () {
  console.log("进行了一次新计算");
  let result = 0;
  const len = arguments.length;
  for (let i = 0; i < len; i++) {
    result += arguments[i];
  }
  return result;
};
// 为求和方法创建代理
const proxyAddAll = (function () {
  // 求和结果的缓存池
  const resultCache = {};
  return function () {
    // 将入参转化为一个唯一的入参字符串
    const args = Array.prototype.join.call(arguments, ",");
    // 检查本次入参是否有对应的计算结果
    if (args in resultCache) {
      // 如果有,则返回缓存池里现成的结果
      return resultCache[args];
    }
    return (resultCache[args] = addAll(...arguments));
  };
})();
function cacheProxy(fn) {
  const cache = Object.create(null);
  return (...args) => {
    const key = args.map(arg => JSON.stringify(arg)).join("__");
    if (cache[key]) {
      console.log("命中缓存");
      return cache[key];
    }
    const res = fn.apply(this, args);
    cache[key] = res;
    return res;
  };
}
function sum(n1, n2) {
  return n1 + n2;
}
const proxySum = cacheProxy(sum);
console.log(proxySum(3, 5)); // 8
console.log(proxySum(3, 5)); // 命中缓存 8
webpack devServer 正向(客户端)代理
参考 webpack.docschina.org/configurati…
// webpack.config.js
module.exports = {
  // 其他配置...
  devServer: {
    proxy: {
      "/api": "http://localhost:8081",
    },
  },
};
nginx 反向(服务端)代理
nginx 配置文件可参考 www.runoob.com/w3cnote/ngi…
server {
    listen   8000;
    location / {
        proxy_pass http://localhost:8001;
    }
    location /api/ {
        proxy_pass http://localhost:8002;
        proxy_set_header Host $host;
    }
}
Proxy
跟踪属性访问
const user = {
  name: "张三",
};
const proxy = new Proxy(user, {
  get(target, key) {
    console.log("get...");
    return Reflect.get(target, key);
  },
  set(target, key, val) {
    console.log("set", val);
    return Reflect.set(target, key, val);
  },
});
proxy.name = "李四"; // set 李四
console.log(proxy.name); // get... 李四
隐藏属性
const user = {
  name: "张三",
  age: 25,
  girlfriend: "小红",
};
const hiddenProps = ["girlfriend"]; // 要隐藏的属性 key
const proxy = new Proxy(user, {
  get(target, key) {
    if (hiddenProps.includes(key as string)) return undefined;
    return Reflect.get(target, key);
  },
  has(target, key) {
    if (hiddenProps.includes(key as string)) return false;
    return Reflect.has(target, key);
  },
  set(target, key, val) {
    if (hiddenProps.includes(key as string)) return false;
    console.log("set...", val);
    return Reflect.set(target, key, val);
  },
});
console.log(proxy.girlfriend); // undefined
console.log(proxy.girlfriend); // undefined
console.log("girlfriend" in proxy); // false
验证属性
const user = {
  name: "张三",
  age: 25,
};
const proxy = new Proxy(user, {
  get(target, key) {
    return Reflect.get(target, key);
  },
  set(target, key, val) {
    if (key === "age") {
      if (typeof val !== "number") return false; // 验证 age 类型
    }
    return Reflect.set(target, key, val);
  },
});
proxy.age = "a";
console.log(proxy.age); // 25
function sum(n1, n2) {
  return n1 + n2;
}
const proxySum = new Proxy(sum, {
  apply(target, thisArg, argumentsList) {
    const [num1, num2] = argumentsList;
    const isNumber = n => typeof n === "number";
    if (!isNumber(num1) || !isNumber(num2)) {
      throw new TypeError("args is must be number");
    }
    return Reflect.apply(target, thisArg, argumentsList);
  }
});
proxySum(3, 6); // 必须传入数字
记录实例
const userList = new WeakSet(); // 每次初始化 user ,都记录到这里
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
const ProxyUser = new Proxy(User, {
  construct(...args) {
    const user = Reflect.construct(...args);
    userList.add(user); // 记录 user 对象
    return user;
  },
});
const user1 = new ProxyUser("张三");
const user2 = new ProxyUser("李四");
console.log("userList", userList);
注意事项
捕获器不变式
- 捕获器即 get ,不变式即不能因为 Proxy 而改变对象本身的描述符特性
const obj = { x: 100, y: 0 };
Object.defineProperty(obj, "y", {
  value: 200,
  writable: false,
  configurable: false,
});
const proxy = new Proxy(obj, {
  get() {
    return "abc";
  },
});
console.log(proxy.x);
console.log(proxy.y); // y 属性描述符被修改,proxy 不能修改它的值
this
- 函数里的 this 是由执行时确认的,而非定义时
const user = {
  name: "张三",
  getName() {
    console.log("this...", this);
    return this.name;
  },
};
const proxy = new Proxy(user, {});
user.getName(); // 执行时 this 是 user
proxy.getName(); // 执行时 this 是 proxy
装饰器模式 对比 代理模式
- 装饰器模式:层层装饰,增强其能力
- 代理模式:控制其访问能力,隐藏底层具体的操作
转载自:https://juejin.cn/post/7197753883422163002




