第一节 响应式对象的流程

124 阅读4分钟

在mini-vue中,是通过reactive()方法,传入一个JavaScript对象作为参数,该方法需要传入一个参数,通过尾调用的方式执行了createReactiveObject方法,该方法传入三个参数,第一个是reactive()方法传入的对象参数(也就是要创建为响应式对象的源对象),第二个是收纳了响应式对象的WeakMap容器,第三个参数是处理Proxy对象的对象(包括了getter和setter),该方法的主要流程是:首先通过第一个参数->源对象,通过Map对象的get方法查询第一个对象是否有代理对象存在在第二个参数所指向的Map对象中,如果有则命中前面的缓存,如果没有则新建一个代理对象,并且将创建完成之后的代理对象存到这个Map对象中,最后该方法返回传入的源对象所对应的代理对象。

其实大家对vue的官方文档撰写的响应式原理进行阅读之后,就会发现,vue的响应式对象是需要进行依赖收集和触发依赖的,而这两个关键的操作,就存在新建代理对象这一个步骤中,所以接下来我们要对新建代理这一步进行剖析。

在新建代理对象的过程中,毫无疑问要进行代理的对象是源对象,也就是reactive方法传入的对象,而另一个包含着getter和setter的对象,以下称baseHandlers,则是要分析的一个关键。baseHandlers包含的get方法,主要是通过Reflect.get方法,将被代理的目标对象所对应的属性值传到一个叫res的变量中,再通过track(target,"get",key)方法进行依赖收集,最后将res返回。

而track方法则是通过一个模块中的全局对象targetMap来查询是否收集过该target的依赖容器,在这一步,targetMap存储的键值对是 key:源对象 : value:该对象所对应的Map对象(作为存储该对象依赖的容器),紧接着通过track方法传入的key参数->对象的某个属性,来查询这个依赖容器中是否存在该属性所对应的依赖集合Set,如果有则返回该集合,然后通过检查这个集合中是否包含当前的effect,如果没有当前的effect则添加该effect对象进去该集合。

baseHandlers包含的set方法,同样是调用reflect的set方法来实现对源对象的改变,同时当对对象进行修改的时候,要调用trigger(target,"get",key)来触发依赖,trigger方法主要是将包含所有依赖的Map对象从targetMap中取出,然后再取出对应的key的依赖集合,参考执行get方法的收集过程,最后将这些依赖集合中的元素依次进行调用。

那响应式如何实现呢?其实就是通过get过程来收集依赖和set过程触发依赖来实现的。

学习vue的你,相信对effect方法有所耳闻。

Vue 通过一个副作用 (effect)  来跟踪当前正在运行的函数。副作用是一个函数的包裹器,在函数被调用之前就启动跟踪。Vue 知道哪个副作用在何时运行,并能在需要时再次执行它。 引用自官方文档

如果我们有一个响应式对象let count = reactive({num: 0}),那么现在我们已经知道这个count是一个响应式对象。接下来我们通过effect(() => (lummy = count.num))在effect方法传入一个函数,首先我们会创建一个ReactiveEffect对象,该对象的构造函数参数是effect方法的入参,紧接着我们调用ReactiveEffect对象的run方法,run方法将该this(该ReactiveEffect对象)存入到模块全局变量activeEffect中,再执行函数() => (lummy = count.num),当执行这个函数的时候,很明显将触发响应式对象的get方法,执行get方法的时候,首先将进行对应源对象的依赖Map的查询,再查询对应的key的依赖集合,如果都没有,那么都需要进行初始化,也就是新建Map以及新建Set,最后将这个依赖收集起来,其实这里依赖指的就是这个函数了,因为刚刚已经将这个函数存入了全局变量activeEffect中,那么此时就可以通过activeEffect来访问该函数,并且将该函数存入到get过程中所调用的函数track方法中的依赖集合Set中,存储完成后,该函数()=>(lummy = count.num)结果已经返回,此时lummy = 0。此时若将count.num的值进行修改,那么将触发响应式对象的set方法,该set方法,会将收集到的依赖依次进行调用,此时lummy = count.num会被再执行一次,那么此时lummy将始终等于count.num的值。

希望大家能多给意见,有什么写得不对的请帮忙指出