Vue的data、computed、watch源码浅谈

2020-06-16 06:52:31易采站长站整理

watcher的运行路径就是: 开始 -> ParentWatcher -> SonWatcher -> ParentWatcher -> 结束。

是不是特别像函数运行中的入栈出栈,没错,Vue内部就是用了栈的数据结构来记录watcher的运行轨迹。


// watcher栈
const targetStack = []

// 将上一个watcher推到栈里,更新Dep.target为传入的_target变量。
export function pushTarget(_target) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}

// 取回上一个watcher作为Dep.target,并且栈里要弹出上一个watcher。
export function popTarget() {
Dep.target = targetStack.pop()
}

有了这些辅助的工具,就可以来看看Watcher的具体实现了


import Dep, { pushTarget, popTarget } from './dep'

export default class Watcher {
constructor(getter) {
this.getter = getter
this.get()
}

get() {
pushTarget(this)
this.value = this.getter()
popTarget()
return this.value
}

update() {
this.get()
}
}

回顾一下开头示例中Watcher的使用。


const data = reactive({
msg: 'Hello World',
})

new Watcher(() => {
document.getElementById('app').innerHTML = `msg is ${data.msg}`
})

传入的getter函数就是


() => {
document.getElementById('app').innerHTML = `msg is ${data.msg}`
}

在构造函数中,记录下getter函数,并且执行了一遍get


get() {
pushTarget(this)
this.value = this.getter()
popTarget()
return this.value
}

在这个函数中,this就是这个watcher实例,在执行get的开头先把这个存储了渲染函数的watcher设置为当前的Dep.target,然后执行this.getter()也就是渲染函数

在执行渲染函数的途中读取到了data.msg,就触发了defineReactive函数中劫持的get:


Object.defineProperty(data, key, {
get() {
dep.depend()
return val
}
})

这时候的dep.depend函数:


depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}

所收集到的Dep.target,就是在get函数开头中pushTarget(this)所收集的


new Watcher(() => {
document.getElementById('app').innerHTML = `msg is ${data.msg}`
})

这个watcher实例了。

此时我们假如执行了这样一段赋值代码:


data.msg = 'ssh'