使用代理可以在代码中实现一些有用的编程模式。
9.3.1 跟踪属性访问
通过捕获get、set和has等操作,可以知道对象属性什么时候被访问、被查询。
把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:
const user = {name: 'Jack'};const proxy = new Proxy(user, {get(target, property, receiver) {console.log(`获取 ${property}`);return Reflect.get(...arguments);},set(target, property, value, receiver) {console.log(`设置 ${property} = ${value}`);return Reflect.set(...arguments);}});proxy.name;proxy.age = 27;// 获取 name// 设置 age = 27
9.3.2 隐藏属性
代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。
比如:
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);}},set(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// set()console.log('foo' in proxy); // trueconsole.log('bar' in proxy); // trueconsole.log('baz' in proxy); // true
9.3.3 属性验证
因为所有赋值操作都会触发set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:
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
9.3.4 函数与构造函数参数验证
跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。
比如,可以让函数只接收某种类型的值
9.3.5 数据绑定与可观察对象
通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。
比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中.
另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息
9.4 小结
代理是ECMAScript 6新增的新特性。不支持向后兼容。
从宏观上看,代理是真实JavaScript对象的透明抽象层。
代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分JavaScript的基本操作和方法。
在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式。
与代理如影随形的反射API,则封装了一整套与捕获器拦截的操作相对应的方法。
可以把反射API看作一套基本操作,这些操作是绝大部分JavaScript对象API的基础。
代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象。
