Vue的data、computed、watch源码浅谈(2)

又因为渲染函数可以是嵌套运行的,比如在Vue中每个组件都会有自己用来存放渲染函数的一个watcher,那么在下面这种组件嵌套组件的情况下:

// Parent组件 <template> <div> <Son组件 /> </div> </template>

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'

就会运行到劫持的set函数里:

Object.defineProperty(data, key, { set(newVal) { val = newVal dep.notify() } })

此时在控制台中打印出dep这个变量,它内部的deps属性果然存储了一个Watcher的实例。

Vue的data、computed、watch源码浅谈

运行了dep.notify以后,就会触发这个watcher的update方法,也就会再去重新执行一遍渲染函数了,这个时候视图就刷新了。

computed

在实现了reactive这个基础api以后,就要开始实现computed这个api了,这个api的用法是这样:

const data = reactive({ number: 1 }) const numberPlusOne = computed(() => data.number + 1) // 渲染函数watcher new Watcher(() => { document.getElementById('app2').innerHTML = ` computed: 1 + number 是 ${numberPlusOne.value} ` })

vue内部是把computed属性定义在vm实例上的,这里我们没有实例,所以就用一个对象来存储computed的返回值,用.value来拿computed的真实值。

这里computed传入的其实还是一个函数,这里我们回想一下Watcher的本质,其实就是存储了一个需要在特定时机触发的函数,在Vue内部,每个computed属性也有自己的一个对应的watcher实例,下文中叫它computedWatcher

先看渲染函数:

// 渲染函数watcher new Watcher(() => { document.getElementById('app2').innerHTML = ` computed: 1 + number 是 ${numberPlusOne.value} ` })

这段渲染函数执行过程中,读取到numberPlusOne的值的时候

首先会把Dep.target设置为numberPlusOne所对应的computedWatcher

computedWatcher的特殊之处在于

渲染watcher只能作为依赖被收集到其他的dep筐子里,而computedWatcher实例上有属于自己的dep,它可以收集别的watcher作为自己的依赖。

惰性求值,初始化的时候先不去运行getter。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/7b6b423cd2e0b54ace04d2a47559c167.html