initState
initState
initState ⽅法主要是对 data、 props 、 methods 、 computed 和 wathcer 等属性做了初 始化操作。
initState
比如 data , data 的初始化主要过程也是做两件事,⼀个是对定义 data 函数返回对象的遍历,通过 proxy 把每⼀个值 vm._data.xxx 都代理到 vm.xxx 上;另⼀个是调用 observe 方法观测整个 data 的变化,把 data 也变成响应式,可以通过 vm._data.xxx 访问到定义 data 返回函数中对应的 属性。
proxy 就是数据代理, 把 vm._data.xxx 代理都 vm.xxx上。
initData
observe
observe 就是给 非VNode 的对象类型数据添加⼀个 Observer ,如果已经添加过则直接 返回,否则在满足⼀定条件下去实例化⼀个 Observer 对象实例。
observe
Observer 是⼀个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新:
首先实例化 Dep 对象(这里的dep 是保存数组类型数据的依赖,对象的会在当前的defineReactive创建),接着通过执行 def 函数把 自身实例 添加到数据对象 value 的 __ob__ 属性上,数组会调⽤ observeArray 方法, 纯对象调用 walk 方法。
observeArray 是遍历数组再次调⽤ observe 方法; walk ⽅法是遍历对象的 key 调方 defineReactive$$1 方法
Observer
def
defineReactive$$1 函数最开始初始化 Dep 对象的实例,接着拿到 obj 的属性描述符,然后对子对象 递归调用 observe 方法,这样就保证了无论 obj 的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 obj 中⼀个嵌套较深的属性,也能触发 getter 和 setter。最后利用 Object.defineProperty 去给 obj 的属性 key 添加 getter 和 setter。
defineReactive$$1
依赖收集
上面代码中多次出现了 Dep ,Dep 是整个 getter 依赖收集的核心,它有⼀个静态属性 target , 这是⼀个 全局唯一 Watcher ,因为在同⼀时间只能有⼀个全局的 Watcher 被计算,另外它的属性 subs 一个元素为 Watcher 的数组。
Dep
结合 watcher 讲讲 Dep
Watcher
Vue 的 mount 中的 mountComponent 函数
mountComponent
实例化了一个 渲染 Watcher, Watcher 执行了 this.get() 方法;
首先 pushTarget(), Dep.target = 当前的渲染 Watcher ;
再执行 this.getter , 也就是 updateComponent 方法:首先在 _render() 渲染 Vnode 过程中会访问 vm 的data,就触发了每个数据 getter 方法;
每个数据 都有一个 dep;
每个数据 都有一个 dep
在触发 getter 的时候会调用 dep.depend() 方法,也就会执行 Dep.target.addDep(this) ;
Dep.target 已经为 渲染 Watcher, 也就是执行 watcher.addDep(dep);
注意: 在 watcher.addDep 中,this.newDeps.push(dep), watcher 保存了dep 的引用;dep.addSub(this) dep 也添加了watcher ,这是一个双向保存的过程,两者都保存了对方。
addDep
dep.addSub(this) 也就是执行 this.subs.push(sub),在当前 data 中的这个数据 的 dep.subs 中 放入 当前的 Watcher,这个⽬的是为后续数据变化时候能通知到哪些 subs 做准备。
所以在 vm._render() 过程中,会触发所有数据的 getter,这样实际上已经完成了⼀个依赖收集的过程。
在执行完 Watcher 的 get 方法后 if (this.deep) { traverse(value) } ,会递归访问 value,触发它所有子元素 的 getter;
接着 popTarget(),Dep.target = targetStack.pop() ,把 Dep.target 恢复成上⼀个状态,因为当前 vm 的数据依赖收集已经完成,那么对应的 渲染 Dep.target 也需要改变。
收集依赖的目的是为了当这些响应式数据发送变化,触发它们的 setter 的时候,能知道应该通知哪 些订阅者去做相应的逻辑处理。
派发更新
当我们重新设置 data 中的数据的时候,就会 触发 setter, setter 就会 notify 该数据的 dep.subs 收集的 watcher。
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
遍历所有的 subs,就是 Watcher 的 实例数组,然后调用每个 watcher 的 update 方法;
update
首先它会用 has 判断 保证同一个 watcher 只能添加一次;通过 wating 保证对 nextTick(flushSchedulerQueue) 的调用逻辑只有⼀次;
这里有一个队列的概念,是 Vue 在做派发更新的时候的一个优化点,它并不会每次数据修改都会触发 watcher 的回调,而是把这些 watcher 先添加到一个队列里,然后在 nextTick 后执行flushSchedulerQueue。
首先会进行 队列排序, 接着进行遍历, 拿到对应的 watcher 执行 run()。
在遍历的时候 每次都对 queue.length 求值,因为在 watcher.run() 的时候,很可能会添加新的 watcher,这样会再次执行 queueWatcher,而此时的 flushing 为 true, 就会走 else 逻辑,,然后就会从后往前找,找到第⼀个待 插⼊ watcher 的 id 比当前队列中 watcher 的 id 大的位置。把 watcher 按照 id 的插⼊到队列 中,因此 queue 的长度发送了变化。
flushSchedulerQueue
先通过 this.get() 得到它当前的值,然后做判断,如果 满足新旧值不等、新值是对象类型、 deep 模式任何⼀个条件,则执行 watcher 的回调,注意回调 函数执行的时候会把第一个和第⼆个参数传入新值 value 和旧值 oldValue ,这就是当我们添加自定义 user watcher 的时候能在回调函数的参数中拿到新旧值的原因。
那么对于渲染 watcher 而言,它在执行 this.get() 方法求值的时候,会执行 getter ⽅法:
updateComponent = () => { vm._update(vm._render(), hydrating) }
所以这就是当我们去修改组件相关的响应式数据的时候,会触发组件重新渲染的原因,接着就会重新执行 patch 的过程,后面的过程 大部分和首次渲染相同,有一点小区别。
run
执行完 run, 往后执行 resetSchedulerState 就是 恢复状态,把一些变量恢复初始值,把 watcher 队列清空。
resetSchedulerState
总结
Vue 数据修改派发更新实际上就是当数据发生变化的 时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher ,都触发它们的 update 过程,这个过程⼜利用了队列做了进⼀步优化,在 nextTick 后执行所有 watcher.run() ,最后执行它们的回调函数。
setter---> dep.notify() ---> subs[i].update() ---> queueWatcher ---> nextTick(flushSchedulerQueue)










网友评论