从这一p开始,我们将学习 非原始值的响应式方案
认识Reflect
Reflect是一个全局对象, 它提供了拦截 JavaScript 操作的方法.
1 | const obj = {foo:1} |
这两个操作是等价的,事实上,Reflect.get还有第三个参数receiver,可以把它理解为函数中的this
1 | const onj = {foo:1} |
这对我们改良响应式原理是有帮助的.我们先让问题暴露出来
1 | const obj = { |
我们希望能在obj.foo++的时候,能触发响应式系统,但是使用return target[key]
的时候,this指向的是原始对象,而不是代理对象,所以无法触发响应式系统.
等价于
1 | effect(()=>{ |
所以我们会在这里使用Reflect函数
Vue3的响应式原理
如何代理Object
拦截in操作符
使用拦截函数 has
1 | const obj = { foo:1 } |
拦截for…in循环
使用拦截函数 ownKeys
1 | const obj = { foo:1 } |
为什么需要ITERATE_KEY呢?
拦截ownKeys操作即可间接拦截for…in循环.这个拦截函数与grt/set不同,在set/get中,我们可以得到具体操作的key,但是在ownKeys中,我们只能拿到目标对象target.
这也很符合直觉,在读写属性的时候,我们清楚的知道读写的是哪一个属性.而ownKeys用来获取一个对象所有的键值,这个操作明显不与任何具体的键值进行绑定,所以我们要构造唯一的key作为标识.
既然追踪的是ITERATE_KEY,那么相应的在触发响应的是时候也要触发他track(target,ITERATE_KEY)
那么在什么情况下触发与ITERATE_KEY相关联的响应呢?
1 | const obj = { foo:1 } |
当我们给p添加新属性bar的时候,我们期望会触发副作用函数(因为key的个数变成了两个),触发ITERATE_KEY的响应.而在目前set拦截函数接收到的key只有bar,所以触发trigger的时候也是会执行与bar相关的副作用函数
我们又又又要完善一下trigger函数
1 | function trigger(target,key){ |
但是,如果是仅仅修改原属性的值的操作,而不是添加新属性,这也会导致触发ITERATE_KEY的副作用函数,显然这是不必要的.
解决这个问题的核心是要能在set函数内能区分添加新属性还是设置原有属性的操作
1 | const p = new Proxy(obj,{ |
我们也要对应的修改一下trigger函数
1 | function trigger(target,key,type){ |
通常我们会将操作类型封装成一个枚举类型.
1 | const TriggerType = { |
删除属性操作的代理
可以使用拦截函数 deleteProperty
1 | const p = new Proxy(obj,{ |
delete操作也会触发ITERATE_KEY的副作用函数.在trigger函数中加上type === 'DELETE'
的判断即可,不再赘述.
写于西13