Skip to content

Latest commit

 

History

History
152 lines (105 loc) · 4.84 KB

代理与反射.md

File metadata and controls

152 lines (105 loc) · 4.84 KB

代理与反射

ES6新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力。

在ES6之前,ECMAScript中并没有类似代理的特性。由于代理是一种新的基础性语言能力,很多转译程序都不能把代理行为装换为之前的ECMAScript代码。因此,代理和反射只能在百分百支持它们的平台上使用

const target = {
  id: 'target'
};
const handler = {};

const proxy = new Proxy(target, handler);

//id属性会访问同一个值,修改目标值 会反映在na'ge
console.log(target.id); // target
console.log(proxy.id); // target

Proxy.prototype是undefined,因此不能使用instanceof操作符

定义捕获器

使用代理的主要目的是可以定义捕获器

const target = {
  id: 'bar'
};
const handler = {
  get() {
    return 'handler oberride';
  }
};

const proxy = new Proxy(target, handler);

这样,当通过代理对象执行get()操作的时候,就会触发定义的get()捕获器。当然,这里的get()不是JS对象可以调用的方法。这个操作是在JS代码中通过多种形式触发并被get()捕获器拦截到。如下代码,proxy[property]、proxy.property或Object.create(proxy)[property]等操作都会触发基本的get()操作以获取属性。

console.log(proxy.id); // handler oberride
console.log(proxy['id']); // handler oberride
console.log(Object.create(proxy)[id]); // handler oberride

捕获器参数

捕获器会接受到目标对象、要查询的属性和代理对象三个参数。

const target = {
  foo: 'bar'
};
const handler = {
  get(trapTarget, property, receiber) {
    console.log(trapTarget === target); // 目标对象
    console.log(property);  // 要查询的属性
    console.log(receiber === proxy); // 代理对象
  }
};
const proxy = new Proxy(target, handler);

proxy.foo;
// true
// foo
// true

所有捕获器都可以基于自己的参数重建原始操作,但是并被所有捕获器行为都像get()那么简单。因此手动编写代码如法炮制的想法是不现实的。因此全局封装了Reflect对象。

反射API(Reflect对象)

![image-20220207210829594](/Users/jaydonyin/Library/Application Support/typora-user-images/image-20220207210829594.png)

apply调用函数用的,construct是通过new操作符初始化实例用的。

其他的十一个都是和对象相关的。例如定义属性、删除属性、访问属性、设置属性等等。

因此,如果我们想创建一个可以捕获所有方法,然后将每个方法转发给对应反射API的空代理,那么甚至不需要定义处理程序对象:

const proxy = new Proxy(target, Reflect);
// 等同于下方
const proxy = new Proxy(target, {});

实用反射API

在这些情况下应该优先实用反射API,这是有一些理由的。

  1. 反射API与对象API

    1. 反射API并不限于捕获处理程序

    2. 大多数反射API方法在Object类型上有对应的方法。通常Object适用于通用程序,而反射方法适用于颗粒度的对象控制与操作。

      例如:Object.keys()只能枚举可枚举属性,Reflect.ownKeys()可以枚举全部属性。可以对应Object.getOwnPropertyNames()作用一样。

  2. 状态标记

    Object.deineProperty会返回创建的对象,这个对象实际上没什么用。

    Reflect.deineProperty会返回创建与否的布尔值,状态标记。

  3. 用一等函数代替操作符

    image-20220207221949563

    ![image-20220207222136814](/Users/jaydonyin/Library/Application Support/typora-user-images/image-20220207222136814.png)

捕获器不变式

当一个对象的属性不可配置且不可写时,不能通过捕获器返回一个和该属性不同的的值,会抛出TypeError;

可撤销代理

有时候需要终端代理对象和目标对象之间的联系。

Proxy暴露了rebocable()方法,这个方法支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的。撤销函数revoke()是幂等的,调用几次结果都一样。撤销代理之后再调用代理会抛出异常。

const target = {
  foo: 'bar'
};
const handle = {
  get() {
    return 'intercepted'
  }
}

const { proxy, revoke } = Proxy.revocable(target, handle);
console.log(proxy.foo  ); //'intercepted'
代理捕获器与反射方法,见书P274

代理应用一个有趣的例子

var a = new Proxy([], Reflect.ownKeys(Reflect).reduce((handlers, key)=> {
  handlers[key] = (...args) => {
    console.log(key, ...args)
    return Reflect[key](...args)
  }
  return handlers
}, {}))

通过反射API监听数组的变化,例如push一个元素,会打印出全过程

数组的length属性是可修改的,对象的不可以