概念
- 代理与反射 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
Proxy 的特性- 实例 和 代理对象 属性会访问同一个值- 给目标属性赋值会反映在两个对象上, 因为两个对象访问的是同一个值- 给代理属性赋值会反映在两个对象上 , 因为这个赋值会转移到目标对象- Proxy.prototype 是undefined , 因此不能使用instanceof 操作符- 严格相等可以用来区分代理和目标 `console.log(target === proxy); // false`> 书中提到捕获器 , 本质就是 能够在代理时候 对相应的数据进行捕获 ,然后进行操作, 比如 get | set```javascriptconst target = {foo: 'bar'};const handler = {// 捕获器在处理程序对象中以方法名为键get() {return 'handler override';}};const proxy = new Proxy(target, handler);// 触发 get 的时机console.log(target.foo); // barconsole.log(proxy.foo); // handler overrideconsole.log(target['foo']); // barconsole.log(proxy['foo']); // handler overrideconsole.log(Object.create(target)['foo']); // barconsole.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
通过反射 Reflect, 可以基于自己的参数重建原始操作。 <br />处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法, 这些方法与捕获器拦截<br />的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。- Reflect.get()```javascriptconst target = {foo: 'bar'};const handler = {get() {return Reflect.get(...arguments)}// 或者 简化get: Reflect.get};const proxy = new Proxy(target, handler);proxy.foo;
撤销代理
有时候可能需要中断代理对象与目标对象之间的联系。
Proxy 也暴露了revocable()方法,这个方法支持撤销代理对象与目标对象的关联。 撤销代理的操作是不可逆的。
const target = {foo: 'bar'};const handler = {get() {return 'intercepted';}};const { proxy, revoke } = Proxy.revocable(target, handler);console.log(proxy.foo); // interceptedconsole.log(target.foo); // barrevoke(); // 撤销代理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
代理的不足
- this 的指向
 - 代理 与内部槽位
 
const target = new Date();const proxy = new Proxy(target, {});console.log(proxy instanceof Date); // trueproxy.getDate(); // TypeError: 'this' is not a Date object
代理捕获器 get() 与反射方法 set()
get()
get()捕获器会在获取属性值的操作中被调用。对应的反射API 方法为Reflect.get()。
const myTarget = {};const proxy = new Proxy(myTarget, {get(target, property, receiver) {console.log('get()');return Reflect.get(...arguments)}});proxy.foo;// get()
set()
set()捕获器会在设置属性值的操作中被调用。对应的反射API 方法为Reflect.set()。
const myTarget = {}const proxy = new Proxy(myTarget, {set(target, key, value, receiver) {console.log('set()');return Reflect.set(...arguments)}})proxy.foo = 'bar';// set()
返回值: 返回true 表示成功;返回false 表示失败,严格模式下会抛出TypeError。
has()
用来判断 目标对象,是否存在某个属性
has()捕获器会在in 操作符中被调用。对应的反射API 方法为Reflect.has()。
const myTarget = {};const proxy = new Proxy(myTarget, {has(target, property) {console.log('has()');return Reflect.has(...arguments)}});'foo' in proxy;// has()
has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。
defineProperty()
defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射API 方法为
Reflect.defineProperty()。
getOwnPropertyDescriptor()
代理模式
使用代理可以在代码中实现一些有用的编程模式。
跟踪属性访问
通过捕获get、set 和has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获
器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:
const user = {name: 'Jake'};const proxy = new Proxy(user, {get(target, property, receiver) {console.log(`Getting ${property}`);return Reflect.get(...arguments);},set(target, property, value, receiver) {console.log(`Setting ${property}=${value}`);return Reflect.set(...arguments);}});proxy.name; // Getting nameproxy.age = 27; // Setting age=27
隐藏属性
代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。比如:
const hiddenProperties = ['foo', 'bar'];const targetObject = {foo: 1,bar: 2,baz: 3};const proxy = new Proxy(targetObject, {get(target, property) {if (hiddenProperties.includes(property)) {return undefined; // 隐藏} else {return Reflect.get(...arguments);}},has(target, property) {if (hiddenProperties.includes(property)) {return false; // // 隐藏} else {return Reflect.has(...arguments);}}});// get()console.log(proxy.foo); // undefinedconsole.log(proxy.bar); // undefinedconsole.log(proxy.baz); // 3// has()console.log('foo' in proxy); // falseconsole.log('bar' in proxy); // falseconsole.log('baz' in proxy); // true
属性验证
const target = {onlyNumbersGoHere: 0};const proxy = new Proxy(target, {set(target, property, value) {if (typeof value !== 'number') {return false;} else {return Reflect.set(...arguments);}}});proxy.onlyNumbersGoHere = 1;console.log(proxy.onlyNumbersGoHere); // 1proxy.onlyNumbersGoHere = '2';console.log(proxy.onlyNumbersGoHere); // 1
