本文通过简单的例子分析 renderWatcher、userWatcher、computedWatcher 的初始和更新流程,分解了它们执行函数流程,以及 Dep、Watcher 的关系,实例数据结构。
renderWatcher
var vm = new Vue({
el: '#app',
template: `
<div @click="change">
{{msg}}
</div>
`,
data() {
return {
msg: 1
}
},
methods: {
change() {
this.msg = Math.random();
}
}
})
初始化:
{
msg(dep): 1,
__ob__: Observer (dep)
}
$mount -> mountComponent -> renderWatcher
此时 Watcher 里 lazy = false -> get() -> vm._update(vm._render(), hydrating)
执行 render 函数生成 Vnode 过程中, msg 的 Dep subs[renderWatcher] targetStack[renderWatcher]
生成 DOM
处理 get() 里的 finally
popTarget 取栈顶元素, msg Dep.target = undefined
cleanupDeps 作用是 depIds,deps 保存上一次, newDeps newDepIds 保存当前的
点击更新:
点击:
msg Dep notify 循环处理subs,执行每个 watcher.update()
-> queueWatcher
-> nextTick(flushSchedulerQueue)
将 flushSchedulerQueue 包装成函数放到 callbacks 中,timerFunc 将 flushCallbacks 放到微任务中
-> subs 循环完成
-> flushCallbacks -> flushSchedulerQueue
queueWatcher
has 避免相同 Watcher 重复入 queue
flushing 标志在执行 flushSchedulerQueue 时会改为 true,表示正在执行更新,不往 queue 添加观察者
waiting 标志 让重复queueWatcher 时, nextTick(flushSchedulerQueue) 只执行一次
nextTick
pending 标志 nextTick 首次执行为 true , flushCallbacks 执行为 false,也就是同一次事件循环中,所有微任务一起执行。
var vm = new Vue({
el: '#app',
template: `
<div @click="change">
{{msg}}
</div>
`,
data() {
return {
msg: 1
}
},
methods: {
change() {
this.msg = Math.random();
}
},
created() {
this.$nextTick(() => { console.log(1) })
this.$nextTick(() => { console.log(2) })
this.$nextTick(() => { console.log(3) })
}
})
执行 3 次 nextTick ,第一次执行时触发微任务,所有同步代码执行完,执行 flushCallbacks,这时 callbacks [f1,f2,f3],依次执行。
嵌套的 nextTick
var vm = new Vue({
el: '#app',
template: `
<div @click="change">
{{name}}
</div>
`,
data() {
return {
name: 'vvv'
}
},
methods: {
change() {
this.name = 'cc'
this.$nextTick(() => {
this.name = 'dd'
this.$nextTick(() => { console.log('第二个 $nextTick') })
})
}
},
})
解析:
初始完成后:
name 的 Dep: subs[renderWatcher], renderWatcher: depIds deps保存 name 的 Dep
点击更新
this.name = 'cc'
这一步通知 name 的 Dep 中 subs 中的所有 Wathcer 更新,此时只有一个 renderWatcher
将 renderWatcher 放到 queue ,而 queue 中的执行是在 flushSchedulerQueue 触发,
flushSchedulerQueue 的触发是在 flushCallbacks 中执行,其中的 callbacks 的值是由 nextTick 中放入,flushCallbacks 是个微任务。
所以 nextTick(flushSchedulerQueue) 执行完,flushCallbacks 不会执行,执行下一步同步代码
this.$nextTick(cb)
将 cb 处理 放到 callbacks 中,此时不会产生新的微任务 pending = true
调用栈空后,处理 callbacks,先执行 name 的更新,然后是 change 中 的 nextTick 的回调,从这可以看到 nextTick 可以拿到更新后的值。
回调 name 重新赋值和执行 nextTick 和上述一样。又来一个轮回,两次是独立的。因为外层的 name 更新完,会重置原来的参数。
userWatcher
$watch 源码:
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
// 对象会转出函数 再次调用
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
// 立即执行回调
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
// vm._watchers 移除, deps 里 Watcher 移除
watcher.teardown()
}
}
var vm = new Vue({
el: '#app',
template: `
<div @click="change">
{{name}}
</div>
`,
data() {
return {
name: 'vvv'
}
},
methods: {
change() {
this.name = 'dxx'
}
},
})
vm.$watch('name', {
handler(neV, oldV) {
console.log({ neV, oldV })
},
immediate: true
})
分析:
new Vue 中,name 收集[renderWatcher],执行 vm.$watch 时,将回调函数进行处理,最终执行
// userWatcher
new Watcher(vm, 'name', handler(neV, oldV) {
console.log({ neV, oldV })
}, {
immediate: true
user: true
});
实例中
vm._watchers [renderWatcher, userWatcher]
user = true
lazy = false
cb = handler
getter
function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
执行 getter
function (vm) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
// vm.name 触发 getter
obj = obj[segments[i]]
}
// 'vvv'
return obj
}
name 收集变成 [renderWatcher, userWatcher]
immediate=true 回调执行
结束
name 的 Dep 与 renderWatcher、userWatcher 关系
点击更新执行情况
$watch 的回调是在 userWatcher 的 run 方法里
watch 中 的 deep 属性用来深度观察
var vm = new Vue({
el: '#app',
template: `
<div @click="change">
{{name}}
</div>
`,
data() {
return {
name: {
b: 0
}
}
},
methods: {
change() {
this.name.b = Math.random();
}
},
watch: {
name: {
handler: function (neV, oldV) {
console.log({ neV, oldV })
},
// deep: true
}
},
})
当 b 改变时,watch 回调不会触发,加上 deep:true 就能深度监测。因为监听的是 name 属性,b 的 dep 中没有 userWatcher,所以在 traverse(value) 中进行 getter 收集。
computed Watcher
options.computed
vm._computedWatchers[key] = new Watcher(
vm,
getter || noop, // typeof userDef === 'function' ? userDef : userDef.get
noop,
{lazy: true} // 惰性求值
)
// defineComputed 代理 vm 下
// 最终执行的函数 getter
function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
var vm = new Vue({
el: '#app',
template: `
<div @click="change">
{{someComputedProp}}
<br />
{{b}}
</div>
`,
data() {
return {
a: 0,
b: 0,
}
},
methods: {
change() {
this.b = Math.random();
}
},
computed: {
someComputedProp() {
return this.a + 1;
}
}
})
分析:
initComputed 创建 computedWatcher 不会进行 get()->
$mount -> renderWatcher->
render 函数中 -> 访问 someComputedProp -> computedGetter
watcher.evaluate()
computedWatcher中 getter 执行,即 someComputedProp() { return this.a + 1;} 执行。
someComputedProp 执行时 this.a 触发 a 的 Dep subs[computedWatcher] 收集,newDeps [aDep]
someComputedProp 返回 1
对 computedWatcher cleanupDeps,-> computedWatcher.deps [aDep]
computedWatcher.value = 1
解释下 Dep Watcher 函数干了什么?
targetStack 全局变量栈,存放 Watcher,Dep.target 指向栈顶
Watcher
get
1、将 当前 Watcher 放到 targetStack;
2、value = watcher.getter(); 触发收集
3、deep 为 true 触发深度收集($watch 设置 deep true);
将当前 Watcher 弹出 targetStack;
cleanupDeps:watcher.deps/depIds 存放 Watcher(从 news 中的拿的,其赋值是在 addDep 中)
4、返回 value
addDep
1、将有关 的 Dep 放到 watcher.newDepIds/newDeps
2、Dep.subs 中添加 Watcher
Dep
depend
Dep.target.addDep(this) 调用 Watcher.addDep(Dep)
所以执行到上述阶段(Dep.target 之前):
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
开始 targetStack = [renderWatcher, computedWatcher], Dep.target= computedWatcher;
this.a -> aDep.depend() -> aDep.subs=[computedWatcher] ,computedWatcher.newDeps=[aDep]
finally 执行后
targetStack = [renderWatcher] Dep.target= renderWatcher
computedWatcher.deps=[aDep], computedWatcher.newDeps=[]
computedWatcher.value = 1
继续 watcher.depend 分析
depend () {
// this computedWatcher
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
遍历 watcher.deps 数组即 依赖的 Dep,执行 depend
depend () {
if (Dep.target) {
// renderWatcher.addDep(aDep)
Dep.target.addDep(this)
}
}
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// aDep subs[computedWatcher, renderWatcher]
dep.addSub(this)
}
}
}
最终的关系
更新流程
当 b 改变,通知 renderWatcher 更新和上面 renderWatcher 流程一样,重新渲染,区别是多了 computedWatcher,而 a 没变 -> someComputedProp 也没变,执行 createComputedGetter时,computedWatcher.dirty = false,不走其渲染,剩下和初始化流程一样。
再来看个改变的例子
var vm = new Vue({
el: '#app',
template: `
<div @click="change">
{{someComputedProp}}
<br />
{{b}}
</div>
`,
data() {
return {
b: 0,
}
},
methods: {
change() {
this.b = Math.random();
}
},
computed: {
someComputedProp() {
return this.b + 1;
}
}})
点击时,this.b 改变 notify [computedWatcher, renderWatcher],computedWatcher.update 是将 dirty = true,renderWatcher 走 queueWatcher 流程,最终微任务中是 renderWatcher -> run 重新收集,和初始流程一样,只不过值存在,就不添加了。