如何利用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
Uh oh!
There was an error while loading. Please reload this page.
-
本文所需知识: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