1 背景知识
ES6添加了一些内建对象,赋予开发者更多访问JS引擎的能力。proxy(代理)是一种可以拦截并改变底层JS引擎操作的包装器,在新语言中通过它暴露内部运作的对象。调用new Proxy()可创建代替其他目标对象的代理,它虚拟化了目标,所以二者看起来功能一致。代理可以拦截JS引擎内部目标的底层对象操作,这些底层操作被拦截后会触发响应特定操作的陷阱函数;反射API以Reflect对象的形式出现,对象中方法的默认特性与相同的底层操作一致,而代理可以覆写这些操作,每个代理陷阱对应一个命名和参数都相同的Reflect方法。
下图总结了代理陷阱的特性
每个陷阱覆写JS对象的一些内建特性,可以用他们拦截并修改这些特性,如果仍然需要使用相应的内建特性,则可以使用相应的反射API方法。
2 创建一个简单的代理
- 用Proxy构造函数创建代理需要传入两个参数:目标(target)和处理程序(handle). 处理程序用于定义一个或者多个陷阱的对象,在代理中,除了专门操作定义的陷阱外,其余操作均使用默认特性,不适用任何陷阱的处理程序等价于简单的代理转发:
let target = {};
let proxy = new Proxy(target, {});
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
target.name = "target";
console.log(proxy.name); // "target"
console.log(target.name); // "target"
- 这个示例中代理将所有的操作都转发到目标,将“proxy”赋值给proxy.name属性上时会在目标上创建name,代理只是简单的将操作转发给目标,它不会存这个属性,由于proxy.name和target.name引用的都是target.name。 因此二者的值相同,从而为target.name设置新值后,proxy.name也一同变化
3 使用set陷阱验证属性
- 假设创建一个属性值是数字的对象,对象中每增加一个属性都要加以验证,如果不是数字必须抛出错误,为了实现这个任务,可以定义个set陷阱来覆写设置值的默认特性。set陷阱接受四个参数:
- trapTarget 用于接受属性(代理的目标)的对象
- key 要 写入的属性键(String or Symbol)
- value 被写入属性的值
- receiver 操作发生的对象(通常是代理) (可选)
- Reflect.set()是set陷阱对应的反射方法和默认特性,它和set代理陷阱一样也接受相同的4个参数,以方便在陷阱中使用。如果属性已设置陷阱应该返回true,如果未设置则返回false。(Reflect.set()方法基于操作是否成功来返回恰当的值)
- set代理陷阱可以拦截写入属性的操作,get代理陷阱可以拦截读取属性的操作
4 用get陷阱验证对象结构(Object Shape)
- 只有当读取属性时才会检验属性,所以无论对象中是否存在某个属性,都可以通过get陷阱来检测,它接受3个参数:
- trapTarget 被读取属性的源对象(代理的目标)
- key 要读取的属性键(字符串或Symbol)
- receiver 操作发生的对象(通常是代理)
- 由于get陷阱不写入值,所以它复刻了set陷阱中除value外的其他3个参数,Reflect.get()也接受同样3个参数并返回属性的默认值
5 使用has陷阱隐藏已有属性
- 可用in操作符来检测给定的对象是否具有某个属性,如果自有属性或者圆形属性匹配到这个名称或者Symbol返回true;
let target = {
value: 42
}
console.log("value" in target) // true
console.log("toString" in target) // true
- value是自有谁能够 toString 是继承Object的属性,二者在对象上都存在,用in检测都返回true。在代理中可以使用has陷阱可以拦截这些in操作并返回一个不同的值,每当使用in 操作符时都会调用has陷阱,并传入2个参数:
- trapTarget 读取属性的对象
- key 要检查的属性键(字符串或Symbol)