You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constlogger=()=>console.log('我被执行');constobj={a: 'a'};constproxy=onChange(obj,logger);console.log(proxy.a);// 我被执行 in get trapproxy.b='b';// 我被执行 in set trapdeleteproxy.a;// 我被执行 in deleteProperty trap
Understanding JavaScript Proxies by Examining on-change Library
JavaScript 中的 Proxy 是 ES6 的新语法,可以利用这个强大的特性来优雅的解决各种问题。这篇文章里通过写一个 on-change 库从而理解 Proxy。所以 on-change 这个库是用来做什么用的?这是一个为了观察 object 或 array 改变的库。来看一个简单的例子:
有几个需要注意的点:
onChange 是一个接收两个参数的方法:需要观察的对象和当对象改变时运行的回调函数,最后返回改变后的对象。
在代码第 17 行,当定义 foo 为 true 时,回调函数 logger 被执行。
在代码第 20 行,当定义一个多嵌套的对象时,即便是数组,回调函数 logger 会被执行。这说明 onchange 是递归检查执行,所以修改多嵌套里的属性也会触发回调。
所以现在看看应该如何使用 JS 中 Proxy 语法来重新创建这个 on-change 工具库,首先需要学习 Proxy 的基础语法使用。
什么是 Proxy ?
思考以下这段代码
someObject.prop1
输出 Awesome,someObject.prop2
输出 undefined 因为 prop2 这个属性不存在。假设当访问一个不存在的值时就返回其默认值,在这里访问someObject.prop2
应该返回噢上帝,你访问的属性不存在
而不是 undefined。怎样如何实现而无需在someObject
修改或添加新属性?此刻 Proxy 登场。在牛津英文词典中 proxy 这词代表的意思是代表其他人的权利。这正是 JS 中的 Proxy。Proxy 是 ES6 中的一部分,通过 Proxy 可以创建一个代理,用于替代另一个对象(目标),这个代理对目标对象进行了虚拟。Proxy 允许我们对 object 进行拦截一些操作(如设置值或删除值)的执行。当访问一个对象属性时,发生了如下图所示的事:
当使用 Proxy 时,流程发生了一些变化:
从上图可看出,Proxy 位置在 Object 与 Program 中间调解值的改变。Proxy 会检查 Object 的属性值,如果不存在那也可以返回响应。现在来看看如何在 JS 中创建,不过在此之前应该先熟悉以下术语:
target 目标对象
traps 陷阱,意为将要拦截的操作。例如,在对象里访问一个属性称为 get traps,设置一个属性值称为 set traps,删除一个属性值称为 deleteProperty trap。还有许多 traps 可查阅这里
handler 处理器 一个包含所有 traps 的对象,包含它的描述信息
创建 Proxy 对象
首先要做的就是创建一个需要被观察的 object:
现在需要考虑什么对象操作是需要被 traps 拦截的。这里首先添加了一个 get traps,这个 trap 将会在 handler 中注册:
有几个需要注意的点:
现在我们使用 Proxy 构造函数结合 handler 和 originalObject
整段代码如下
如果浏览器支持 Proxy 语法,那么可以运行在 console 中,或直接使用命令行 node 运行。如果直接打印出 proxiedObject.firstName 属性那么将会输出
现在来修改 handler 来处理不存在属性的情况
当 property 属性存在 receiver 对象时则说明该属性是存在于对象里的,否则返回提示。现在当你访问 proxiedObject.thisPropertDoesNotExist 将会返回
这次将不会再返回 undefined 而是自定义的返回消息。需要注意的是,如果没有定义任何 traps,则正常的传值就像 proxy 不存在。
重新写一个 on-change 库
理解了 proxy 基础语法和如何工作后,我们将重写 on-change 这个库。正如文章开头部分所描述,onChange 是一个接收两个参数的函数:需要观察的对象和当对象改变时运行的回调函数。第一步先创建一个函数:
首先梳理下目前需要做的:希望每当 objToWatch 对象改变时都将执行 onChangeFunction。也就是说当一个属性被访问或修改,或新添加一个属性,或删除一个属性的情况。
在 onChange 函数中返回一个空 handler 的 proxy 对象。因为没有定义 traps 所以任何操作都是传值到目标对象。
因为理解了 get trap 所以首先将注意力放在 当一个属性被访问或修改 这个条件上。当返回属性值时将执行 onChangeFunction,于是写出下面的代码:
现在将注意力放在 添加或删除一个属性 上。当设置一个属性值或者修改值时触发的是 set trap:
注意这里改写并使用 Reflect API(Proxy 里的 trap 也推荐使用 Reflect)。
Reflect.get()
是 get trap 对应的反射方法,同时也是 set 操作的默认行为。Reflect.set()
同理。Reflect 对象所代表的是反射接口,是给底层操作提供默认行为的方法的集合,这些操作是能够被代理重写的。每个代理 trap 都有一个对应的反射方法,每个方法都与对应的 trap 函数同名,并且接收的参数也与之一致。下表总结了这些行为:
可以点击这里阅读了解为什么最好使用 Reflect API。通过查阅表了解到 deleteProperty trap 则是使用
Reflect.deleteProperty()
,既可添加 deleteProperty 完成最后一个触发条件 删除一个属性:到此 on-change 的改写就完成的差不多了,可以通过 console 测试:
可看到控制台将输出三次的 "我被执行"。但是如果 obj 为数组且数组中包含一个对象的话,那么修改这个数组里的对象将不会触发 logger 函数。举个例子
[1, 2, {a: false}]
当设置array[2].a = true
将不会触发 logger。可以通过简单的方式来处理这个 bug,通过判断 Reflect.get() 的返回值,如果值为 object 则返回一个新的 proxy:
这样 onChange 便可观察 array 与 object 的变化。是不是对 proxy 认知更清晰了呢?这篇文章涉及的只是 proxy 的基础知识,更深入扩展建议阅读 understanding ecmascript 6 第十二章 proxy 篇。
The text was updated successfully, but these errors were encountered: