组件库项目中运用的代理模式 | 青训营笔记

90 阅读2分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 8 天

1. 介绍

针对一个对象

设置代理,控制这个对象的访问

用户不得直接访问对象,而要通过代理

2. 举例

房产中介

明星经纪人:明星搞艺术不谈钱,你去和经纪人谈钱,明星在和经纪人谈钱

3. 代理模式

1)UML 类图

image-20230112130039682

2)代码演示

 class RealImg {
   fileName: string;
   constructor(fileName: string) {
     this.fileName = fileName;
   }
   display() {
     this.loadFromDist();
     console.log("display...", this.fileName);
   }
   private loadFromDist() {
     console.log("loading...", this.fileName);
   }
 }
 ​
 class ProxyImg {
   fileName: RealImg;
   constructor(fileName: string) {
     this.fileName = new RealImg(fileName);
   }
   display() {
     this.fileName.display();
   }
 }
 ​
 const n = new ProxyImg("xiaochai");
 n.display();

3)是否符合设计原则

代理和目标分离,解耦

代理可自行扩展

目标也可自行扩展

4. 代理模式 - 场景

DOM 事件代理(委托)

Webpack devServer proxy 正向代理

Nginx 反向代理

1)DOM 事件代理

事件绑定到父容器上,而非目标节点

适合目标较多或数量不确定(如无线加载的瀑布流图片列表)

 const container = document.getElementById("container");
 if (container) {
   // DOM 事件代理(委托)
   container.addEventListener("click", event => {
     const target = event.target as Element;
     if (target.nodeName === "A") {
       alert(target.innerHTML);
     }
   });
 }

2)Webpack devServer proxy 正向代理

开发环境,前端请求服务端 API

代理到本地服务器,或者使用 mock 接口

正向代理

 // webpack.config.js
 {
   devServer: {
     port: 3000,
     proxy:{
       '/api': 'http://localhost:8081' // 用nodejs起一个服务器
     }
   }
 }
 ​
 // 发起请求
 import axios formm 'axios'
 document.getElementById('btn1')?addEventListener('click', () => {
   axios.get('/api/getInfo')
     .then(res => console.log(res.data))
 })

3)Nginx 反向代理

image-20230112134047139

4)总结

DOM 事件代理(委托)

Webpack devServer proxy 正向代理

Nginx 反向代理

5. Proxy 语法

模拟:明星经纪人

Proxy 的应用场景

Proxy 可能遇到的坑

1)模拟:明星经纪人

 // 明细
 const star = {
   name: "张三",
   age: 25,
   phone: "18611112222",
   price: 0, // 明星不谈钱,俗
 };
 ​
 // 经纪人
 const agent = new Proxy(star, {
   get(target, key) {
     if (key === "phone") {
       return "13966667777"; // 经纪人电话,明星电话不能泄露
     }
     if (key === "price") {
       return 100 * 1000; // 明星报价
     }
     return Reflect.get(target, key);
   },
   set(target, key, val): boolean {
     if (key === "price") {
       if (val < 100 * 1000) {
         throw new Error("报价太低,合作失败");
       } else {
         console.log("圆子不错,合作成功");
         Reflect.set(target, key, val);
         return true;
       }
     }
     // 其他属性不可设置
     return false;
   },
 });
 ​
 console.log(agent.name);
 console.log(agent.age);
 console.log(agent.phone);
 console.log(agent.price);
 // agent.price = 666;
 agent.price = 110000;
 console.log(agent.price);

2)Proxy 使用场景

跟踪属性访问

隐藏属性

验证属性

记录实例

A. 跟踪属性访问

 // 1. 跟踪属性访问
 const user = {
   name: "张三",
 };
 ​
 const proxy = new Proxy(user, {
   get(...args) {
     console.log("get..."); // 监听
     return Reflect.get(...args);
   },
   set(target, key, val) {
     console.log("set..."); // 监听
     return Reflect.get(target, key, val);
   },
 });
 ​
 proxy.name;
 proxy.name = "李四";

B. 隐藏属性

 const hiddenProps = ["girlfriend"];
 const user = {
   name: "张三",
   age: 25,
   girlfriend: "小红",
 };
 ​
 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.get(target, key);
   },
   set(target, key, val) {
     if (hiddenProps.includes(key as string)) return false;
     return Reflect.get(target, key, val);
   },
 });
 ​
 proxy.girlfriend = "小丽"; // error 抛错
 console.log(proxy.girlfriend); // undefined
 console.log("girlfriend" in proxy); // false 因为has里做了隐藏

C. 验证属性格式

 const user = {
   name: "张三",
   age: 25,
 };
 ​
 const proxy = new Proxy(user, {
   set(target, key, val) {
     if (key === "age") {
       if (typeof val !== "number") return false;
     }
     return Reflect.get(target, key, val);
   },
 });

D. 记录实例

 const userList = new Set(); // 每次初始化 user,都记录在这里
 class User {
   name: string;
   constructor(name: string) {
     this.name = name;
   }
 }
 ​
 const ProxyUser = new Proxy(User, {
   construct(...agrs) {
     const user = Reflect.construct(...agrs);
     userList.add(user); // 记录
     return user;
   },
 });
 ​
 const user1 = new ProxyUser("张三");
 const user2 = new ProxyUser("李四");
 console.log(userList);

3)Proxy 可能遇到的坑

捕获器不变式

关于 this

A. 捕获器不变式

 // 1. 捕获器不变式 - 如果你原有的属性进行了一些描述符限制,代理则不能再去更改原有对象的属性的一些限制
 // 代理之前一定要先看看,属性的描述符是否符合你的预期
 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);
 ​

代理之前一定要先看看,属性的描述符是否符合你的预期

B. this

 const user = {
   name: "张三",
   getName() {
     console.log("this...", this); // this 在执行中确定的
     return this.name;
   },
 };
 ​
 const proxy = new Proxy(user, {});
 ​
 // user.getName()  在这里执行 this 就是 user
 proxy.getName(); // 这里就是 proxy

代理的方法里面存在 this 的时候,一定要注意这个问题

4)总结

模拟:明星经纪人

Proxy 的应用场景

  • 属性跟踪、属性隐藏、类型校验、记录实例

Proxy 可能会遇到的坑

6. 总结

1)内容回顾

概念介绍,解决的问题

代码演示 和 UML 类图

应用场景 + Proxy 语法

2)重要细节

正向代理(客户端代理) vs 反向代理(服务端代理)

3)注意事项

注意 Proxy 语法的几个坑