就是指给目标对象定义一个代理对象,然后把它当做抽象的目标对象使用 因此它能够用作目标对象的替身,又完全独立于目标对象。
代理是一种新的基础性语言能力,转义程序不能直接将其转换为ES6之前的代码, 因此代理和反射需要在百分百支持它们的平台上使用。
9.1.1 创建空代理 功能: 创建代理参数: param 1: 目标对象param 2: 处理程序对象区分代理和目标: instanceof Proxy,因为Proxy.prototype是undefined
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const target = { id : 'target' }; const handler = {};const proxy = new Proxy (target, handler);console .log (target.id ); console .log (proxy.id ); target.id = `foo` ; console .log (target.id ); console .log (proxy.id ); proxy.id = `bar` ; console .log (target.id ); console .log (proxy.id ); console .log (target.hasOwnProperty ('id' )); console .log (proxy.hasOwnProperty ('id' )); console .log (proxy === target); console .log (target instanceof Proxy );console .log (proxy instanceof Proxy )
9.1.2 定义捕获器 使用代理的主要目的就是可以定义捕获器(trap)。 代理能在操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const target = { foo : 'bar' } const handler = { get ( ) { return 'handler override' ; } } const proxy = new Proxy (target, handler);console .log (target.foo ); console .log (proxy.foo ); console .log (target['foo' ]) console .log (proxy['foo' ]) console .log (Object .create (target)['foo' ]); console .log (Object .create (proxy)['foo' ]);
9.1.3 捕获器参数和反射API trapTarget: 目标对象property: 要查询的属性receiver: 代理对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ;
通过这些参数就可以重建目标对象的原始行为了:
1 2 3 4 5 6 7 8 9 10 11 const target = { foo :'bar' } const handler = { get (trapTarget, property, receiver ) { return trapTarget[property]; } } const proxy = new Proxy (target, handler);console .log (proxy.foo ); console .log (target.foo );
通过全局对象Reflect的同名方法也可以轻松实现重建。
1 2 3 4 5 const handler = { get ( ) { return Reflect .get (...arguments ); } }
1 2 3 const handler = { get :Reflect .get }
1 const proxy = new Proxy (target, Reflect );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const target = { foo : 'bar' , baz : 'qux' }; const handler = { 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 ); console .log (target.foo ); console .log (proxy.baz ); console .log (target.baz );
9.1.4 捕获器不变式 当捕获器定义出现过于反常的行为时(如get和writable的定义出现冲突时) 捕获器不变式会进行报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const target = {};Object .defineProperty (target, 'foo' , { configurable : false , writable : false , value : 'bar' }); const handler = { get ( ) { return 'qux' ; } }; const proxy = new Proxy (target, handler);console .log (proxy.foo );
9.1.5 可撤销代理 这个方法支持撤销代理对象与目标对象的关联。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const target = { foo :'bar' } const handler = { get ( ) { return 'interecpted' } } const { proxy, revoke } = Proxy .revocable (target, handler);console .log (proxy.foo ); console .log (target.foo ); revoke ();console .log (proxy.foo );
9.1.6 实用反射API
反射API与对象API
反射API并不限于捕获处理程序 大多数反射API方法在Object类型上有对应的方法 Object上的方法适用于通用程序 反射方法适用于细粒度的对象控制与操作
状态标记
数据类型: 布尔值 表示: 意图执行的操作是否成功 1 2 3 4 5 6 7 const o = {}try { Object .defineProperty (o, 'foo' , 'bar' ); console .log ('success' ); } catch (e) { console .log ('failure' ); }
1 2 3 4 5 6 const o = {};if (Reflect .defineProperty (o, 'foo' , { value : 'bar' })) { console .log ('success' ); } else { console .log ('failure' ); }
Reflect.defineProperty() Reflect.preventExtensions() Reflect.setPrototypeof() Reflect.set() Reflect.deleteProperty()
用一等函数替代操作符
Reflect.get(): 代替对象属性访问操作符 Reflect.set(): 代替=赋值操作符 Reflect.has(): 代替in操作符或with() Reflect.deleteProperty(): 代替delete操作符 Reflect.construct(): 代替new操作符
安全地应用函数
主要针对apply方法的调用:
1 Function .prototye .apply .call (myFunc, thisVal, argumentList);
1 Reflect .apply (myFunc, thisVal, argumentsList);
9.1.7 代理另一个代理 代理完全可以代理另一个代理,因此可以在一个目标对象之上构建多层拦截网。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 );
9.1.8 代理的问题与不足 直觉上讲,this通常指向调用这个方法的对象。 即调用代理上的任何方法,如proxy.outerMethod(), proxy.outerMethod()调用this.innerMethod(), 实际上会调用proxy.innerMethod()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const wm = new WeakMap ();class User { constructor (userId ) { wm.set (this , userId); } set id (userId ) { wm.set (this , userId); } get id () { return wm.get (this ); } } const user = new User (123 );console .log (user.id ); const userInstanceProxy = new Proxy (user, {});console .log (userInstanceProxy.id );
这是因为User实例一开始使用目标对象作为WeakMao的键, 代理对象却尝试从自身取得这个实例。
把代理User实例改为代理User类本身, 再创建代理的实例。
1 2 3 const UserClassProxy = new Proxy (User , {});const proxyUser = new UserClassProxy (456 );console .log (proxyUser.id );
有些ECMAScript内置类型可能会依赖代理无法控制的机制, 原因是这些内置类型方法的执行依赖this值上的内部槽位, 但是代理对象上不存在这个内部槽位。
1 2 3 4 const target = new Date ();const proxy = new Proxy (target, {});console .log (proxy instanceof Date ); proxy.getDate ();