响应式数据的最终目标,是当对象本身或对象的属性发生变化时,会运行一些函数,最常见的就是render函数
在具体表现上,具体会用到几个核心部件
Observer的目标非常简单,就是把一个普通的对象变为响应式对象
为了实现这一点,Observer把对象的每个属性通过 object.defineProperty转换带有getter,setter属性,这样,当访问或设置属性时就有机会做一些别的事
Observer是vue内部的构造器,我们可以通过vue提供的静态方法vue.Observer ( object )间接使用该功能
在组件生命周期中,这件事发生在beforCreate之后,created之前
具体实现上,他会递归遍历对象的所有属性,以完成深度的属性转换
由于遍历只能遍历到对象的当前属性,因此无法检测到将来动态增加和更新的属性,因此,vue提供了 $set 和 $delete 两个实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性
对于数组,vue会更改它的隐式原型,之所以这样做,是vue需要监听那些可能改变数组内容的方法
总之,obServer的目的,就是要让一个对象,他的属性的读取、赋值、以及数组内容的变化都要被vue感知到
这里有两个问题没解决,就是读取属性时要做什么事,而属性变化时要做什么事,这个问题需要依赖Dep去解决
Dep的含义是Dependency,表示依赖的意思
vue会对每个响应式对象的属性、对象本身、数组本身创建一个Dep实例,每个Dep实例都有能力做以下两件事:
当读取响应式对象的某个属性时,它会进行依赖收集:有人用到了我
当改变某个属性时,它会派发更新:那些用我的人听好了,我变了
这里又出现一个问题,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函数,从而让界面重新渲染同时重新记录当前的依赖
现在还剩最后一个问题,就是Dep通知Watcher后,如果Watcher执行重运行对应的函数,就可能导致函数频繁运行,从而导致效率低下,试想,如果一个交给Watcher的函数,它里面用到了属性a、b、c、d, 那么a、b、c、d属性都会记录依赖,于是下面的代码将触发四次更新:
state.a = "new data";
state.b = "new data";
state.c = "new data";
state.d = "new data";
这样显然是不适合的,因此,Watcher收到派发更新的通知时,不会立即执行对应函数,而是把自己交给一个叫调度器的东西
调度器维护一个执行队列,该队列同一个Watcher仅会存在一次,队列中的watcher不是立即执行,它通常会通过一个叫nextTick的工具方法,把这些需要执行的Watcher放到事件循环的微队列中,nextTick的具体做法是通过promise完成的
也就是说,响应式数据发生变化时,render函数的执行是异步的,并且在微队列中