export default class Watcher { constructor(getter, options = {}) { const { computed } = options this.getter = getter this.computed = computed if (computed) { this.dep = new Dep() } else { this.get() } } }
其实computed实现的本质就是,computed在读取value之前,Dep.target肯定此时是正在运行的渲染函数的watcher。
先把当前正在运行的渲染函数的watcher作为依赖收集到computedWatcher内部的dep筐子里。
把自身computedWatcher设置为 全局Dep.target,然后开始求值:
求值函数会在运行() => data.number + 1的途中遇到data.number的读取,这时又会触发'number'这个key的劫持get函数,这时全局的Dep.target是computedWatcher,data.number的dep依赖筐子里丢进去了computedWatcher。
此时的依赖关系是 data.number的dep筐子里装着computedWatcher,computedWatcher的dep筐子里装着渲染watcher。
此时如果更新data.number的话,会一级一级往上触发更新。会触发computedWatcher的update,我们肯定会对被设置为computed特性的watcher做特殊的处理,这个watcher的筐子里装着渲染watcher,所以只需要触发 this.dep.notify(),就会触发渲染watcher的update方法,从而更新视图。
下面来改造代码:
// Watcher import Dep, { pushTarget, popTarget } from './dep' export default class Watcher { constructor(getter, options = {}) { const { computed } = options this.getter = getter this.computed = computed if (computed) { this.dep = new Dep() } else { this.get() } } get() { pushTarget(this) this.value = this.getter() popTarget() return this.value } // 仅为computed使用 depend() { this.dep.depend() } update() { if (this.computed) { this.get() this.dep.notify() } else { this.get() } } }
computed初始化:
// computed import Watcher from './watcher' export default function computed(getter) { let def = {} const computedWatcher = new Watcher(getter, { computed: true }) Object.defineProperty(def, 'value', { get() { // 先让computedWatcher收集渲染watcher作为自己的依赖。 computedWatcher.depend() return computedWatcher.get() } }) return def }
这里的逻辑比较绕,如果没理清楚的话可以把代码下载下来一步步断点调试,data.number被劫持的set触发以后,可以看一下number的dep到底存了什么。
watch
watch的使用方式是这样的:
watch( () => data.msg, (newVal, oldVal) => { console.log('newVal: ', newVal) console.log('old: ', oldVal) } )
传入的第一个参数是个函数,里面需要读取到响应式的属性,确保依赖能被收集到,这样下次这个响应式的属性发生改变后,就会打印出对饮的新值和旧值。
分析一下watch的实现原理,这里依然是利用Watcher类去实现,我们把用于watch的watcher叫做watchWatcher,传入的getter函数也就是() => data.msg,Watcher在执行它之前还是一样会把自身(也就是watchWatcher)设为Dep.target,这时读到data.msg,就会把watchWatcher丢进data.msg的依赖筐子里。
如果data.msg更新了,则就会触发watchWatcher的update方法
直接上代码:
// watch import Watcher from './watcher' export default function watch(getter, callback) { new Watcher(getter, { watch: true, callback }) }
没错又是直接用了getter,只是这次传入的选项是{ watch: true, callback },接下来看看Watcher内部进行了什么处理:
export default class Watcher { constructor(getter, options = {}) { const { computed, watch, callback } = options this.getter = getter this.computed = computed this.watch = watch this.callback = callback this.value = undefined if (computed) { this.dep = new Dep() } else { this.get() } } }
首先是构造函数中,对watch选项和callback进行了保存,其他没变。
然后在update方法中。
update() { if (this.computed) { ... } else if (this.watch) { const oldValue = this.value this.get() this.callback(oldValue, this.value) } else { ... } }
在调用this.get去更新值之前,先把旧值保存起来,然后把新值和旧值一起通过调用callback函数交给外部,就这么简单。
我们仅仅是改动寥寥几行代码,就轻松实现了非常重要的api:watch。
总结。
有了精妙的Watcher和Dep的设计,Vue内部的响应式api实现的非常简单,不得不再次感叹一下尤大真是厉害啊!