如何利用Proxy实现一个响应式对象 #105
zhangyu1818
announced in
zh-cn
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
本文所需知识:Proxy,Reflect
Proxy
是ES6
新增的对象,可以对指定对象做一层代理。当我们从一个对象中获取某一个键时,会触发
Proxy
中get
的拦截,对某一个键赋值的时候,则会触发Proxy
中set
的拦截,依据此特性,可以实现一个简单的观察者模式。简单的观察者模式
在触发
set
拦截的时候,触发注册的观察函数。将观察函数放入队列中,每次触发
set
拦截的时候,触发队列中的观察函数,这样就实现了一个最简单观察者模式。依赖收集问题
这样的方式,以一个比较大的问题就是没有包含依赖收集,参考如下情况:
在第二个观察函数中,并没有使用到
obj.a
,但是在触发set
拦截的时候,会将所有的观察函数统一触发,所以第二个观察函数也被触发了。想要解决这个问题也比较简单,那就是将观察函数中使用的键名
key
和当前所在的观察函数做一个映射,即key => 观察函数[]
,用Map
存储则可表示为Map<key,function[]>
。现在的问题就是如何在对象被观察函数访问时,触发
get
拦截并获取到此对象所在的观察函数,简而言之就是observer(() => console.log(obj.a));
中,如何在obj.a
被访问的时候获取到() => console.log(obj.a)
。依赖收集的实现
在调用观察函数之前,我们可以将该函数存入一个数组中,执行观察函数,观察函数中访问的键触发
get
拦截,从数组中取得最后一项就是当前所在的函数。语言描述比较无力,直接看代码吧,这部分比较长,我会逐行注释解释。
根据输出可以看出,
observerMap
中已经将键对应的观察函数列表储存了下来,接下来只需要在set
拦截中,获取key
对应的观察函数,遍历调用就行了带依赖收集的简单观察者模式
这就是一个简单的、带依赖收集的观察者模式的实现。
现在这个极简版还有非常多的问题,这里列举几个:
observable
对象并且有相同的键名时,会将观察函数存在observerMap
的同一个key
,触发一个对象的更新会导致另一个对象观察函数执行Object.keys
时不会触发观察函数obj.a.b++
,不会触发观察函数解决方法:
WeakMap
做映射,即对象 => <键 => 观察函数Set>
的映射,在拦截函数里需要用target
对象找到当前的键 => 观察函数Set
的映射ownKeys
的拦截,在此拦截里自定义一个key
来存储对应的观察函数get
拦截里将深层对象转为observable
对象deleteProperty
的拦截这种模式在Vue 3中有所使用,Vue 3是将单独的功能分发为单独的包细分管理的,在@vue/reactivity中,特别提到了nx-js/observer-util,我也是学习这个库后,写了本文进行了一个简单的思路总结。
本文参考:
Beta Was this translation helpful? Give feedback.
All reactions