《JS高级程序设计》第9章 代理与反射

113 阅读4分钟

前言

  1. ECMAScript 6 新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。
  2. 可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。
  3. 在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

一、代理基础

  1. 代理是目标对象的抽象。
  2. 代理是真实 JavaScript 对象的透明抽象层。
  3. 代理可以用作目标对象的替身,但又完全独立于目标对象。
  4. 目标对象既可以直接被操作,也可以通过代理来操作。但直接操作会绕过代理施予的行为。
  5. 最简单的代理是空代理,即除了作为一个抽象的目标对象,什么也不做。
  6. 代理是使用 Proxy 构造函数创建的,这个构造函数接收两个参数:目标对象和处理程序对象。
  7. Proxy.prototypeundefined,因此不能使用instanceof操作符。
  8. 严格相等可以用来区分代理和目标。
  9. 捕获器(trap)就是在处理程序对象中定义的“基本操作的拦截器”。
  10. 每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。
  11. 注意,只有在代理对象上执行这些操作才会触发捕获器。
  12. 所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。
  13. 开发者并不需要手动重建原始行为,而是可以通过调用全局Reflect对象上(封装了原始行为)的同名方法来轻松重建。
const target = {
  foo: 'bar',
};
const handler = {
	// 方法一:
  get() {
    return Reflect.get(...arguments);
  },
	// 方法二:
  get: Reflect.get,
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar

// 方法三
const target = {
  foo: 'bar',
};
// 可以捕获所有方法,然后将每个方法转发给对应反射 API 的空代理
const proxy = new Proxy(target, Reflect);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
  1. 每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”(trap invariant)。
  2. 在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式。
  3. Proxy也暴露了revocable()方法,这个方法支持撤销代理对象与目标对象的关联。撤销函数(revoke())是幂等的,调用多少次的结果都一样。撤销函数和代理对象是在实例化时同时生成的。
const target = {
  foo: 'bar',
};
const handler = {
  get() {
    return 'intercepted';
  },
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.foo); // intercepted
console.log(target.foo); // bar
revoke();
console.log(proxy.foo); // TypeError

代理捕获器与反射方法

  1. 对于在代理对象上执行的任何一种操作,只会有一个捕获处理程序被调用。不会存在重复捕获的情况。
  2. get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为Reflect.get()
  3. set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为Reflect.set()
  4. has()捕获器会在in操作符中被调用。对应的反射 API 方法为Reflect.has()
  5. defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射 API 方法为 Reflect.defineProperty()
  6. getOwnPropertyDescriptor()捕获器会在Object.getOwnPropertyDescriptor()中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor()。
  7. deleteProperty()捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect. deleteProperty()
  8. ownKeys()捕获器会在Object.keys()及类似方法中被调用。对应的反射 API 方法为Reflect. ownKeys()
  9. getPrototypeOf()捕获器会在Object.getPrototypeOf()中被调用。对应的反射 API 方法为 Reflect.getPrototypeOf()
  10. setPrototypeOf()捕获器会在Object.setPrototypeOf()中被调用。对应的反射 API 方法为 Reflect.setPrototypeOf()
  11. isExtensible()捕获器会在Object.isExtensible()中被调用。对应的反射 API 方法为 Reflect.isExtensible()
  12. preventExtensions()捕获器会在Object.preventExtensions()中被调用。对应的反射 API 方法为Reflect.preventExtensions()
  13. apply()捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply()
  14. construct()捕获器会在 new 操作符中被调用。对应的反射 API 方法为Reflect.construct()

代理模式

  1. 跟踪属性访问:通过捕获getsethas等操作,可以知道对象属性什么时候被访问、被查询。
  2. 隐藏属性:代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。
  3. 属性验证:因为所有赋值操作都会触发set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值。
  4. 函数与构造函数参数验证:跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。
  5. 数据绑定与可观察对象:通过代理可以把运行时中原本不相关的部分联系到一起。