概念

  1. 代理与反射 Proxy | Reflect 是 ES6 新增的

代理的概念: 可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对
目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

创建 Proxy

代理是使用Proxy 构造函数创建的, 接收两个参数

  • 目标对象
  • 处理程序对象 ```javascript const target = { id: ‘target’ };

const target = {};

const proxy = new Proxy(target, target);

// id 属性会访问同一个值 console.log(target.id); // target console.log(proxy.id); // target

// 给目标属性赋值会反映在两个对象上 // 因为两个对象访问的是同一个值 target.id = ‘foo’; console.log(target.id); // foo console.log(proxy.id); // foo

// 给代理属性赋值会反映在两个对象上 // 因为这个赋值会转移到目标对象 proxy.id = ‘bar’; console.log(target.id); // bar console.log(proxy.id); // bar

// Proxy.prototype 是undefined // 因此不能使用instanceof 操作符 console.log(target instanceof Proxy); // TypeError: Function has non-object prototype console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype

// 严格相等可以用来区分代理和目标 console.log(target === proxy); // false

  1. Proxy 的特性
  2. - 实例 代理对象 属性会访问同一个值
  3. - 给目标属性赋值会反映在两个对象上, 因为两个对象访问的是同一个值
  4. - 给代理属性赋值会反映在两个对象上 因为这个赋值会转移到目标对象
  5. - Proxy.prototype undefined 因此不能使用instanceof 操作符
  6. - 严格相等可以用来区分代理和目标 `console.log(target === proxy); // false`
  7. > 书中提到捕获器 本质就是 能够在代理时候 对相应的数据进行捕获 ,然后进行操作, 比如 get | set
  8. ```javascript
  9. const target = {
  10. foo: 'bar'
  11. };
  12. const handler = {
  13. // 捕获器在处理程序对象中以方法名为键
  14. get() {
  15. return 'handler override';
  16. }
  17. };
  18. const proxy = new Proxy(target, handler);
  19. // 触发 get 的时机
  20. console.log(target.foo); // bar
  21. console.log(proxy.foo); // handler override
  22. console.log(target['foo']); // bar
  23. console.log(proxy['foo']); // handler override
  24. console.log(Object.create(target)['foo']); // bar
  25. console.log(Object.create(proxy)['foo']); // handler override

Reflect 反射

所有捕获器都可以访问相应的参数, 有了参数, 才可以重建被捕获方法的原始行为。
比如 get() 捕获器会接收的参数

  • target 目标对象
  • key 要查询的属性
  • receiver 代理对象 ```javascript const target = { foo: ‘bar’ }; const handler = { get(trapTarget, property, receiver) { console.log(trapTarget === target); console.log(property); console.log(receiver === proxy); } };

const proxy = new Proxy(target, handler); proxy.foo; // true // foo // true

  1. 通过反射 Reflect 可以基于自己的参数重建原始操作。 <br />处理程序对象中所有可以捕获的方法都有对应的反射(ReflectAPI 方法, 这些方法与捕获器拦截<br />的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。
  2. - Reflect.get()
  3. ```javascript
  4. const target = {
  5. foo: 'bar'
  6. };
  7. const handler = {
  8. get() {
  9. return Reflect.get(...arguments)
  10. }
  11. // 或者 简化
  12. get: Reflect.get
  13. };
  14. const proxy = new Proxy(target, handler);
  15. proxy.foo;

撤销代理

有时候可能需要中断代理对象与目标对象之间的联系。
Proxy 也暴露了revocable()方法,这个方法支持撤销代理对象与目标对象的关联。 撤销代理的操作是不可逆的。

  1. const target = {
  2. foo: 'bar'
  3. };
  4. const handler = {
  5. get() {
  6. return 'intercepted';
  7. }
  8. };
  9. const { proxy, revoke } = Proxy.revocable(target, handler);
  10. console.log(proxy.foo); // intercepted
  11. console.log(target.foo); // bar
  12. revoke(); // 撤销代理
  13. console.log(proxy.foo); // TypeError

代理另一个代理

  1. const target = {
  2. foo: 'bar'
  3. };
  4. const firstProxy = new Proxy(target, {
  5. get() {
  6. console.log('first proxy');
  7. return Reflect.get(...arguments);
  8. }
  9. });
  10. const secondProxy = new Proxy(firstProxy, {
  11. get() {
  12. console.log('second proxy');
  13. return Reflect.get(...arguments);
  14. }
  15. });
  16. console.log(secondProxy.foo);
  17. // second proxy
  18. // first proxy
  19. // bar

代理的不足

  • this 的指向
  • 代理 与内部槽位
  1. const target = new Date();
  2. const proxy = new Proxy(target, {});
  3. console.log(proxy instanceof Date); // true
  4. proxy.getDate(); // TypeError: 'this' is not a Date object

代理捕获器 get() 与反射方法 set()

get()

get()捕获器会在获取属性值的操作中被调用。对应的反射API 方法为Reflect.get()。

  1. const myTarget = {};
  2. const proxy = new Proxy(myTarget, {
  3. get(target, property, receiver) {
  4. console.log('get()');
  5. return Reflect.get(...arguments)
  6. }
  7. });
  8. proxy.foo;
  9. // get()

set()

set()捕获器会在设置属性值的操作中被调用。对应的反射API 方法为Reflect.set()。

  1. const myTarget = {}
  2. const proxy = new Proxy(myTarget, {
  3. set(target, key, value, receiver) {
  4. console.log('set()');
  5. return Reflect.set(...arguments)
  6. }
  7. })
  8. proxy.foo = 'bar';
  9. // set()

返回值: 返回true 表示成功;返回false 表示失败,严格模式下会抛出TypeError。

has()

用来判断 目标对象,是否存在某个属性
has()捕获器会在in 操作符中被调用。对应的反射API 方法为Reflect.has()。

  1. const myTarget = {};
  2. const proxy = new Proxy(myTarget, {
  3. has(target, property) {
  4. console.log('has()');
  5. return Reflect.has(...arguments)
  6. }
  7. });
  8. 'foo' in proxy;
  9. // has()

has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。

defineProperty()

defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射API 方法为
Reflect.defineProperty()。

getOwnPropertyDescriptor()

代理模式

使用代理可以在代码中实现一些有用的编程模式。

跟踪属性访问

通过捕获get、set 和has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获
器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:

  1. const user = {
  2. name: 'Jake'
  3. };
  4. const proxy = new Proxy(user, {
  5. get(target, property, receiver) {
  6. console.log(`Getting ${property}`);
  7. return Reflect.get(...arguments);
  8. },
  9. set(target, property, value, receiver) {
  10. console.log(`Setting ${property}=${value}`);
  11. return Reflect.set(...arguments);
  12. }
  13. });
  14. proxy.name; // Getting name
  15. proxy.age = 27; // Setting age=27

隐藏属性

代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。比如:

  1. const hiddenProperties = ['foo', 'bar'];
  2. const targetObject = {
  3. foo: 1,
  4. bar: 2,
  5. baz: 3
  6. };
  7. const proxy = new Proxy(targetObject, {
  8. get(target, property) {
  9. if (hiddenProperties.includes(property)) {
  10. return undefined; // 隐藏
  11. } else {
  12. return Reflect.get(...arguments);
  13. }
  14. },
  15. has(target, property) {
  16. if (hiddenProperties.includes(property)) {
  17. return false; // // 隐藏
  18. } else {
  19. return Reflect.has(...arguments);
  20. }
  21. }
  22. });
  23. // get()
  24. console.log(proxy.foo); // undefined
  25. console.log(proxy.bar); // undefined
  26. console.log(proxy.baz); // 3
  27. // has()
  28. console.log('foo' in proxy); // false
  29. console.log('bar' in proxy); // false
  30. console.log('baz' in proxy); // true

属性验证

  1. const target = {
  2. onlyNumbersGoHere: 0
  3. };
  4. const proxy = new Proxy(target, {
  5. set(target, property, value) {
  6. if (typeof value !== 'number') {
  7. return false;
  8. } else {
  9. return Reflect.set(...arguments);
  10. }
  11. }
  12. });
  13. proxy.onlyNumbersGoHere = 1;
  14. console.log(proxy.onlyNumbersGoHere); // 1
  15. proxy.onlyNumbersGoHere = '2';
  16. console.log(proxy.onlyNumbersGoHere); // 1