watcher
经常会提到computed和watch的原理是什么,因此这里单独说明 Vue源码中 watcher 相关内容。在源码中,分为三类 watcher:
- 渲染
watcher watch用户watchercomputed计算属性 下面将详细说明。
渲染 watcher
在初始化部分已经说明过渲染watcher,它在mountComponent中初始化。
js
export function mountComponent(){
...
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
...
}用户watcher之 watch
它是在initstate中初始化的。它的原理就是对watch的每个属性创建一个watcher对象;watcher 在初始化时会将监听的目标值缓存到 watcher.value 中, 因此触发 data[key] 的 get 方法, 被对应的 dep 进行依赖收集; 当 data[key] 发生变动时触发 set 方法, 执行 dep.notify 方法, 通知所有收集的依赖 watcher , 触发收集的 watcher , 在run中执行 watcher.cb , 也就是 watch 中的监听函数。
js
// this.lazy为true 说明是计算属性
this.value = this.lazy
? undefined
: this.get()计算属性computed
computed 也是在initState中初始化的,不过和watch有所不同,从步骤来看如下:
- 对每个计算属性创建一个
watcher对象core/observer/watcher.js
js
// this.lazy为true 说明是计算属性
this.value = this.lazy
? undefined
: this.get()- 初始化
computed的时候src/core/instance/state.js
js
function defineComputed(target, key) {
...
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get: createComputedGetter(key),
set: noop
})
}这个方法的作用是让computed成为一个响应式数据,并定义它的get属性,当页面执行渲染访问的computed时(也就是render阶段),才会触发get然后执行createComputedGetter。
- createComputedGetter
js
function createComputedGetter (key) { // 高阶函数
return function () { // 返回函数
const watcher = this._computedWatchers && this._computedWatchers[key]
// 这个watcher就是对应的computed-watcher
if (watcher) {
if (watcher.dirty) { // 在实例化watcher时为true,表示需要计算
watcher.evaluate() // 进行计算属性的求值
}
// 让计算属性内的响应式数据订阅当前的render-watcher
if (Dep.target) { // 当前的watcher,这里是页面渲染触发的这个方法,所以为render-watcher
watcher.depend() // 收集当前watcher
}
return watcher.value // 返回求到的值或之前缓存的值
}
}
}
//
class Watcher {
...
evaluate () {
// 计算属性求值
// 这里会触发computed内部可以访问的响应式数据的get,它们会将当前的computed-watcher作为依赖收集到
// 自己的dep里。计算完将dirty设置为false,表示已经计算过了
this.value = this.get()
this.dirty = false // 表示计算属性已经计算,不需要再计算
}
//
depend () {
let i = this.deps.length // deps内是计算属性内能访问到的响应式数据的dep的数组集合
while (i--) {
this.deps[i].depend() // 让每个dep收集当前的render-watcher
}
}
}computed内的响应式数据会收集computed-watcher和render-watcher两个watcher,当computed内的状态发生变更触发set后,首先通知computed需要进行重新计算,然后通知到视图执行渲染,再渲染中会访问到computed计算后的值,最后渲染到页面。
Quiz Time👇
watch 和 computed 使用上有什么区别?
● computed 是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props); ● computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后或者第一次访问 computed 属性,才会计算新的值,而 watch 则是当数据发生变化便会调用执行函数; ● 从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据。