9.1 代理基础
代理简单来说
就是指给目标对象定义一个代理对象,然后把它当做抽象的目标对象使用
因此它能够用作目标对象的替身,又完全独立于目标对象。
代理需要平台支持
代理是一种新的基础性语言能力,转义程序不能直接将其转换为ES6之前的代码,
因此代理和反射需要在百分百支持它们的平台上使用。
9.1.1 创建空代理
Proxy函数
- 功能: 创建代理
- 参数:
- param 1: 目标对象
- param 2: 处理程序对象
- 区分代理和目标:
- 可以使用严格相等(===)来区分
- 不能使用
1 | const target = { |
9.1.2 定义捕获器
使用代理的主要目的就是可以定义捕获器(trap)。
代理能在操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。
注意
只有在代理对象上执行这些操作才会触发捕获器。
1 | const target = { |
9.1.3 捕获器参数和反射API
get()参数
- trapTarget: 目标对象
- property: 要查询的属性
- receiver: 代理对象
1 | const target = { |
通过这些参数就可以重建目标对象的原始行为了:
1 | const target = { |
全局对象Reflect
通过全局对象Reflect的同名方法也可以轻松实现重建。
简洁一点...
1 | const handler = { |
再简洁一点...
1 | const handler = { |
更简洁点...(直接捕获所有方法)
1 | const proxy = new Proxy(target, Reflect); |
使用反射API的样板代码
1 | const target = { |
9.1.4 捕获器不变式
当捕获器定义出现过于反常的行为时(如get和writable的定义出现冲突时)
捕获器不变式会进行报错。
1 | const target = {}; |
9.1.5 可撤销代理
Proxy.revocable
这个方法支持撤销代理对象与目标对象的关联。
1 | const target = { |
9.1.6 实用反射API
1. 反射API与对象API
- 反射API并不限于捕获处理程序
- 大多数反射API方法在Object类型上有对应的方法
- Object上的方法适用于通用程序
- 反射方法适用于细粒度的对象控制与操作
2. 状态标记
- 数据类型: 布尔值
- 表示: 意图执行的操作是否成功
重构前
1 | const o = {} |
重构后(操作失败时不会报错)
1 | const o = {}; |
提供状态标记的反射方法
- Reflect.defineProperty()
- Reflect.preventExtensions()
- Reflect.setPrototypeof()
- Reflect.set()
- Reflect.deleteProperty()
3. 用一等函数替代操作符
- Reflect.get(): 代替对象属性访问操作符
- Reflect.set(): 代替=赋值操作符
- Reflect.has(): 代替in操作符或with()
- Reflect.deleteProperty(): 代替delete操作符
- Reflect.construct(): 代替new操作符
4. 安全地应用函数
主要针对apply方法的调用:
apply调用反面教材
1 | Function.prototye.apply.call(myFunc, thisVal, argumentList); |
使用Reflect来避免
1 | Reflect.apply(myFunc, thisVal, argumentsList); |
9.1.7 代理另一个代理
多层拦截网
代理完全可以代理另一个代理,因此可以在一个目标对象之上构建多层拦截网。
1 | const target = { |
9.1.8 代理的问题与不足
1. 代理中的this
直觉上讲,this通常指向调用这个方法的对象。
即调用代理上的任何方法,如proxy.outerMethod(),
proxy.outerMethod()调用this.innerMethod(),
实际上会调用proxy.innerMethod()
当目标对象依赖于对象标识时会存在问题
1 | const wm = new WeakMap(); |
这是因为User实例一开始使用目标对象作为WeakMao的键,
代理对象却尝试从自身取得这个实例。
解决方案
把代理User实例改为代理User类本身,
再创建代理的实例。
1 | const UserClassProxy = new Proxy(User, {}); |
2. 代理与内部槽位
有些ECMAScript内置类型可能会依赖代理无法控制的机制,
原因是这些内置类型方法的执行依赖this值上的内部槽位,
但是代理对象上不存在这个内部槽位。
1 | const target = new Date(); |