vue 响应式原理的四个核心模块
Observe
Observe
要实现的目标非常简单,就是把一个普通对象转换成响应式对象。
为了实现这一点,Observe
把对象的每个属性通过 Object.defineProperty
转换为带有 setter
和 getter
的属性,这样一来,vue就有机会在设置和访问属性的时候做点别的事情。
Observe
是Vue内部的构造器,我们可以通过Vue提供的静态方法Vue.observe(object)
间接的使用该功能。
在组件的生命周期中,这件事发生在beforeCreate
之后,create
之前。
具体的实现上,它会递归遍历对象中的所有属性,以完成深度的属性转换。
由于遍历时只能遍历到对象的当前属性,无法检测到将来对象动态增加或删除属性的属性,因此Vue提供了$set
和$delete
实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性。
对于数组,Vue会更改它的隐式原型,目的是因为Vue需要监听那些可能改变数组内容的方法。
总之,Observe
的目的,就是要让一个对象。它属性的读取、赋值,内部数组的变化都要能够被Vue监听到。
Dep
这里有两个问题,就是读取属性的时候做什么事,属性变化的时候要做什么事,这个问题需要依靠Dep
来解决。
Dep
的意思是dependency
,表示依赖的意思。
Vue会为响应式对象中的每个属性、对象本身、数组本身创建一个Dep
实例,每个Dep
实例都有能力做以下两件事情:
- 记录依赖:是谁要用我
- 派发更新:我改变了,我要通知那些用到我的人
当读取响应式对象的某个属性时,它会进行依赖收集:有人用到我了;当改变某个属性时,它会派发更新:那些用到我的人听好了,我变了,要更新。
Watcher
这里又出现一个问题,就是Dep
如何知道谁在用我?
要解决这个问题,需要依靠另外一个东西,就是Watcher
。
当某个函数执行的时候,用到了响应式数据,响应式数据是无法知道是哪个函数在用自己的,因此,Vue通过一个巧妙的方法来解决这个问题。
我们不要直接执行函数,而是把函数交给一个Watcher
对象去执行,Watcher
是一个对象,每个这样的函数执行时都应该创建一个Watcher
对象,通过Watcher
去执行。
watcher
会设置一个全局变量,让全局变量记录当前负责执行的watcher
等于自己,然后再去执行函数,当函数执行时,使用到了响应式的数据,发生了依赖记录dep.depend()
,那么dep
就把这个全局变量记录下来,表示有一个watcher
用到了我的属性。
当Dep
进行派发时,它会通知之前记录的所有watcher
,我变化了!
每一个Vue组件都至少对应一个watcher
,该watcher
记录了该组件的render
函数。
watcher
首先会把render
函数执行一遍收集依赖,于是那些在render
中使用到的响应式数据就会记录这个watcher
。
当数据变化时,dep
就会通知该watcher
,而watcher
将重新运行render
函数,从而让界面重新渲染同时重新记录当前的依赖。
Scheduler
现在还剩下随后一个问题,就是Dep
通知watcher
执行运行对应的函数,就有可能造成函数频繁运行,从而导致效率低下。
例如,如果一个交给watcher
的函数,里面用到了a,b,c,d,那么a,b,c,d四个属性都会记录依赖,于是下面的代码将触发四次watcher
,更新四次:
this.a = 'new a';
this.b = 'new b';
this.c = 'new c';
this.d = 'new d';
这样显然是不合适的,因此,watcher
收到派发更新的通知后,不是立即执行函数的,而是把自己交给一个叫调度器的东西。
调度器维护一个执行队列,该队列同一个watcher
只会存在一次,他会同个一个nextTick
的工具方法,把这些需要执行的watcher
放入到事件循环的微队列中,nextTick
的具体做法是通过promise
完成的。
nextTick通过this.$nextTick暴露给开发者;
也就是说,当响应式数据变化时,render
函数的执行是异步的,并且在微队列中。
总体流程
上一篇: 「面试必看」JS百题斩~ 终于明白了原型 与 原型链