代理 Proxy

ES6提供新的对象劫持的能力。可以给目标对象生成一个如同“替身”的代理对象。对代理对象的操作会反射至原目标对象之上。

创建代理

使用new Proxy(target, handler)创建代理对象。

  1. const target = { //目标对象
  2. id: 'target'
  3. };
  4. const handler = {}; //一个空代理
  5. const proxy = new Proxy(target, handler);
  6. // id属性都会访问同一个值
  7. console.log(target.id, proxy.id); // target target
  8. // 给目标属性赋值会反映在两个对象,两个对象访问是同一个值
  9. // 给代理属性赋值也会反映在两个对象,赋值会转移至目标对象
  10. target.id = 'foo';
  11. console.log(target.id, proxy.id); // foo foo
  12. proxy.id = 'bar';
  13. console.log(target.id, proxy.id); // bar bar
  14. console.log(target.hasOwnProperty('id'), proxy.hasOwnProperty('id')); // true true
  15. console.log(target === proxy) // false

Proxy.prototype是undefined,因此无法使用instanceof Proxy

反射 Reflect

所有的捕获器都可以基于自己的参数重建原始操作,但并非所有都像get()那么简单。手动重建原始行为并不现实。
ES6提供全局Reflect对象同名方法来轻松重建这些原始行为,这就是反射。

const target = {
  foo: 'bar',
  baz: 'qux'
};

const handler = {
  /*
  因为Reflect的方法与捕获器拦截方法是相同名称和函数签名且具有与被拦截方法相同行为
  get(){
        return Reflect.get(...arguments);
  }
  甚至更简洁
  get: Reflect.get
  但既然用到代理来拦截,肯定要为原有行为的基础上作代码的修改捕获的方法,
  反射可以用最少的代码进行修改
  */
    get(trapTarget, property, receiver){
    let decoration = '';
    if(property === 'foo'){
        decoration = '!!!';
    }
        return Reflect.get(...arguments) + decoration;
  }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo);   // bar!!!
console.log(target.foo);  // bar

console.log(proxy.baz);   // qux
console.log(target.baz);  // qux

实用反射API

  1. 反射API与对象API
    1. 反射API并不限于捕获处理程序
    2. 大多娄反射API方法在Object类型上有对应方法
      1. Object上方法适用于通用程序
      2. 反射方法笔髟于细粒度的对象控制与操作
  2. 状态标记
    1. 很多反射方法返回称作“状态标记”的布尔值,表示执行操作是否成功
      Reflect.defineProperty(o, 'foo', {value: 'bar'}) //-> true
      1. Reflect.defineProperty()
      2. Reflect.preventExtensions()
      3. Reflect.setPrototypeOf()
      4. Reflect.set()
      5. Reflect.deleteProperty()
  3. 用一等函数替代操作符
    1. Reflect.get():可以替代对象属性访问操作符。
    2. Reflect.set():可以替代=赋值操作符。
    3. Reflect.has():可以替代in操作符或with()。
    4. Reflect.deleteProperty():可以替代delete操作符。
    5. Reflect.construct():可以替代new操作符。
  4. 安全地应用函数
    1. 通过apply方法调用函数时,被调用的函数可能也定义了自己的apply属性(虽然可能性极小)
      Function.prototype.apply.call(myFunc, thisVal, argumentList);
    2. Reflect.apply(myFunc, thisVal, argumentsList);

      捕获器

      捕获器在handler对象中配置

代理捕获器与反射方法

get()

const target = {
    id: 'target'
};
const handler = {
    get(trapTarget, property, receiver){
    console.log(trapTarget === target); // true
    console.log(property); // id
    console.log(receiver === proxy); // true

      return trapTaget[property]; //手动重建原始行为
  }
};
const proxy = new Proxy(target, handler);
console.log(proxy.id); // target

set()
has()
defineProperty()
getOwnPropertyDescriptor()
deleteProperty()
ownKeys()
getPrototypeOf()
setPrototypeOf()
isExtensible()
preventExtensions()
apply()
construct()

捕获器不变式

使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。
例如目标对象有一个不可配置且不可写的数据属性,那么捕获器返回一个与该属性不同的值时,会抛出TypeError

Uncaught TypeError: ‘get’ on proxy: property ‘foo’ is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected ‘bar’ but got ‘qux’)

可撤销代理

const { proxy, revoke } = Proxy.revocable
撤销函数revoke和代理对象proxy是在实例化时同时生成

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

代理另一个代理(套娃)

const target = {
  foo: 'bar'
};

const firstProxy = new Proxy(target, {
  get() {
    console.log('first proxy');
    return Reflect.get(...arguments);
  }
});

const secondProxy = new Proxy(firstProxy, {
  get() {
    console.log('second proxy');
    return Reflect.get(...arguments);
  }
});

console.log(secondProxy.foo);
// second proxy
// first proxy
// bar

代理问题与不足

  1. 代理中的this
    1. 如果目标对象信赖于对象标识,会可能碰到意料之外的问题
  2. 代理与内部槽位 ```javascript const target = new Date(); const proxy = new Proxy(target, {});

console.log(proxy instanceof Date); // true; proxy.getDate(); // TypeError: ‘this’ is not a Date object ```

代理模式

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

  1. 跟踪属性访问
  2. 隐藏属性
  3. 属性验证
  4. 函数与构造函数参数验证
  5. 数据绑定与可观察对象