代理设计模式在Js中的使用

148 阅读3分钟

代理设计模式

定义

一种结构型设计模式,用于提供一个代理对象来控制对另一个对象的访问。 代理对象充当了客户端和被代理对象之间的中介,以控制对被代理对象的访问,并可以在访问过程中进行增强或附加额外的逻辑。

参与者

  1. 抽象主题(Subject):定义了代理对象和真实主题对象共同实现的接口。这样,代理对象就能够替代真实主题对象,对外提供统一的接口。
  2. 真实主题(Real Subject):实现了抽象主题接口,是被代理的真正对象。它执行具体的业务逻辑。
  3. 代理(IProxy):实现了抽象主题接口,并持有一个对真实主题的引用。代理对象接收客户端的请求,并在必要时将请求转发给真实主题对象。代理对象还可以在请求前后执行额外的操作,如权限验证、缓存等。

核心思想

在不改变原有代码的情况下,通过引入一个代理对象来实现对另一个对象的控制和增强。 代理对象在访问过程中可以添加额外的功能,从而对真实对象的访问进行拦截和管理

示例

// 抽象主题接口
interface Subject {
  request(): void;
}

// 真实主题类
class RealSubject implements Subject {
  request(): void {
    console.log("RealSubject handles the request.");
  }
}

// 代理类
class IProxy implements Subject {
  private realSubject: RealSubject;

  constructor(realSubject: RealSubject) {
    this.realSubject = realSubject;
  }

  request(): void {
    // 在请求前或后执行额外操作
    console.log("IProxy handles the request.");
    
    // 转发请求给真实主题对象
    this.realSubject.request();
  }
}

// 客户端代码
const realSubject = new RealSubject();
const iproxy = new IProxy(realSubject);

iproxy.request();

应用场景

  1. 跨域请求代理:在浏览器中,由于同源策略的限制,无法直接从一个域名发起对另一个域名的请求。可以使用代理对象作为中间层,在同源的域名上发起请求,并将请求转发给目标域名。这样就实现了跨域请求的代理。
  2. 缓存代理:在浏览器中,代理对象可以通过缓存服务器响应结果,以减少对真实服务器的请求次数。当客户端发起请求时,代理首先检查是否有缓存的响应结果,如果有,则直接返回缓存的结果;否则,代理会将请求转发给真实服务器,并将响应结果进行缓存。
  3. 图片懒加载:在网页中加载大量图片时,可以使用代理对象来延迟图片的加载。代理对象可以将图片的 src 属性设置为一个占位符或默认图片,只有当图片进入可视区域时再将真实的图片地址赋值给 src 属性,从而实现图片的懒加载。
  4. 权限验证:代理对象可以用于对用户权限进行验证。在浏览器应用程序中,代理对象可以拦截用户请求,并检查用户的身份和权限。只有具备足够权限的用户才能访问被代理的资源或执行特定操作。

ECMAScript 2015

ECMAScript 2015引入了Proxy类。使用Proxy可以方便的对一个对象创建代理对象。

// 被代理对象是target
function target (data) {
    this.data = data;
}

target.name = 'John';
target.age = 30;
target.greet = () => {};

// 拦截策略(功能增强)
const handler = {
  get(target, property, receiver) {
    console.log(`获取属性 "${property}"`);
    return Reflect.get(target, property, receiver);
  },

  set(target, property, value, receiver) {
    console.log(`设置属性 "${property}" 为 ${value}`);
    return Reflect.set(target, property, value, receiver);
  },

  has(target, property) {
    console.log(`检查属性 "${property}" 是否存在`);
    return Reflect.has(target, property);
  },

  deleteProperty(target, property) {
    console.log(`删除属性 "${property}"`);
    return Reflect.deleteProperty(target, property);
  },

  apply(target, thisArg, argumentsList) {
    console.log('调用函数');
    return Reflect.apply(target, thisArg, argumentsList);
  },

  construct(target, argumentsList, newTarget) {
    console.log('调用构造函数');
    return Reflect.construct(target, argumentsList, newTarget);
  },

  getPrototypeOf(target) {
    console.log('获取原型');
    return Reflect.getPrototypeOf(target);
  },

  setPrototypeOf(target, prototype) {
    console.log('设置原型');
    return Reflect.setPrototypeOf(target, prototype);
  },

  defineProperty(target, property, descriptor) {
    console.log(`定义属性 "${property}"`);
    return Reflect.defineProperty(target, property, descriptor);
  },

  getOwnPropertyDescriptor(target, property) {
    console.log(`获取属性描述符 "${property}"`);
    return Reflect.getOwnPropertyDescriptor(target, property);
  },

  ownKeys(target) {
    console.log('获取自身属性键');
    return Reflect.ownKeys(target);
  },
};

// 代理对象是proxy
const proxy = new Proxy(target, handler);

// 通过操作代理对象间接操作被代理对象
console.log(proxy.name); // 获取属性 "name"
proxy.age = 35; // 设置属性 "age" 为 35
console.log('name' in proxy); // 检查属性 "name" 是否存在
delete proxy.age; // 删除属性 "age"
proxy(); // 调用函数
const instance = new proxy(); //调用构造函数 获取属性 "prototype"
console.log(Object.getPrototypeOf(proxy)); // 获取原型
Object.setPrototypeOf(proxy, {}); // 设置原型
Object.defineProperty(proxy, 'city', { value: 'New York' }); // 定义属性 "city"
console.log(Object.getOwnPropertyDescriptor(proxy, 'name')); // 获取属性描述符 "name"
console.log(Object.getOwnPropertyNames(proxy)); // 获取自身属性键