【09】前端常用的设计模式之代理模式

174 阅读2分钟

代理模式

概念

为其它对象提供一种 以代理控制对这个对象的访问。不允许直接访问。

代理模式概念.png

UML 类图

代理模式UML.png

代码演示

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 {
  realImg: RealImg;
  constructor(fileName: string) {
    this.realImg = new RealImg(fileName);
  }
  display() {
    this.realImg.display();
  }
}

const proxyImg = new ProxyImg('xxx.png');
proxyImg.display();

场景

DOM 事件代理

<div id="div1">
  <a href="#">a1</a>
  <a href="#">a2</a>
  <a href="#">a3</a>
</div>

<script>
  const div1 = document.getElementById('div1');
  div1.addEventListener('click', function (e) {
    const target = e.target;
    if (e.nodeName === 'A') {
      console.log(target.innerHTML);
    }
  });
</script>

vite server.proxy

nginx 反向代理

server {
    listen 8000;
    location / {
        proxy_pass http://localhost:8001;
    }
    location /api/ {
        proxy_pass http://localhost:8002;
        proxy_set_header Host $host;
    }
}

反向代理 vs 正向代理

正向代理是客户端的代理(vite proxy serve);反向代理是服务器的代理(nginx)。

Proxy

vue3 使用 proxy 做 data 响应式

// 明星
const star = {
  name: 'yoona',
  age: 20,
  phone: '1866869898',
  price: 0,
};

// 经纪人
const agent = new Proxy(star, {
  get(target, key) {
    if (key === 'phone') {
      return '15989898998'; // 经纪人电话
    }
    if (key === 'price') {
      return 100 * 1000; // 出场费
    }
  },
  set(target, key, val): boolean {
    if (key === 'price') {
      if (val < 100 * 1000) {
        throw new Error('价格低了...');
      } else {
        console.log('报价成功', val);
        return Reflect.set(target, key, val);
      }
    }
    // 其他属性不可以设置
    return false;
  },
});

// 主办方
console.log(agent.name);
console.log(agent.age);
console.log(agent.phone);
console.log(agent.price);
agent.price = 100000000;

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 = 'lie';
console.log(proxy.name);
隐藏属性
const hiddenProps = ['grilfrient']; // 需要隐藏的属性 key

const user = {
  name: '张三',
  grilfrient: '小红',
};

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;
    return Reflect.set(target, key, val);
  },
});

console.log('name', proxy.name); // 张三
console.log('grilfrient', proxy.grilfrient); // undefined
验证属性
// 用TS,会有静态类型检查,用不到这个验证。JS里可以有效果
const user = {
  name: '张三',
};

const proxy = new Proxy(user, {
  get(target, key) {
    return Reflect.get(target, key);
  },
  set(target, key, val) {
    if (key === 'name') {
      if (typeof val !== 'string') return false;
    }
    return Reflect.set(target, key, val);
  },
});

proxy.name = 10;
console.log(proxy.name); // 张三
记录实例
const userList = new WeakSet(); // 每次初始化 useruser,都记录到这里

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);
    return user;
  },
});

const user1 = new ProxyUser('user1');
const user2 = new ProxyUser('user2');
console.log('userList', userList);

Proxy 注意事项

  • 捕获器不变式 这是“红宝书”里的叫法,捕获器即 get,不变式即不能因为 Proxy 而改变对象本身的描述符特性
const obj = { a: 1, b: 2 };
Object.defineProperty(obj, 'b', {
  value: 22,
  writable: false,
  configurable: false,
});

const proxy = new Proxy(obj, {
  get() {
    return 222;
  },
});

console.log(proxy.a); // 222
console.log(proxy.b); // 报错, b 属性描述符被修改, 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

设计原则验证

  • 代理和目标分类,解耦
  • 代理可自行拓展逻辑
  • 目标也可以自行拓展逻辑