Vue源码学习5-响应式原理-Watcher
前言
这篇文章和上篇文章一样,简单的介绍一下Watcher这个class,大致浏览一番即可,此处无需看源码,可略过
--以下vm都代指vue的实例
--以下所有代码来自Vue 2.6.11 版本
贴源码
Watcher对象定义在 src/core/observer/watcher.js 中,未删注释,代码略长。
Watcher,正如其名称一样:观察者。那被观察的东西是什么?就是 Dep对象,也是 data props computed 的值,除了能观测这些,还可以监测Vuex中的 getters ,Vue-Router中的 $route.query
在 defineReactive 源码中我们可以看 const dep = new Dep(),这个dep 会和每个字段的 value 一起通过闭包保存在内存中,并且是一对一绑定的。当 Watcher 观测的value 触发了get逻辑,就会通过dep完成 依赖收集,当触发 set的时候,就会通过dep完成 派发更新。
使用Watcher的地方有这么几个
1:computed,计算属性,这里称之为 computed-watcher
在代码中找到 evaluate 这个方法(译为:估计,评估),这里有求值的意思,这个方法其实是专门给 computed(计算属性)使用的,方法上面的注释也说明了,配合this.lazy字段使用,在后面单独讲 initComputed 逻辑的时候就会知道这玩意是怎么用的了
2:watch,监听器,这里称之为 user-watcher
{ ............... watch:{ xxxx:function(val){} } }
watch 怎么用的就不说了,就是一个数据观测,在 initWatch 的过程中也是会创建Watcher对象(后面单独讲 initWatch 的时候再细说)
3:组件的Watcher,这里称之为 render-watcher
每一个vm中,有且只有一个 render-watcher ,这个watcher是用来触发视图更新的。被render-watcher观测的数据发生变化时,就会由它触发视图的更新,它就是 data 和 view 连接的中间人,触发的方法叫 update 源码中能看到,方法上的注释也说了,当监听的数据发生了变化,就会执行这个方法。
在控制台中打印一个vm可以看到,_watcher就是那唯一一个render-watcher,_watchers是一个数组,会把vm中所有的watcher都装在里面
我们在Watcher的构造函数的逻辑中也可以看到对 _watcher 的赋值,以及对 _watchers 的添加
至于其它方法的作用,单独在这里说没啥意义,下一篇文章开始将会细说响应式的完整流程
/* @flow */ import { warn, remove, isObject, parsePath, _Set as Set, handleError, noop } from '../util/index' import { traverse } from './traverse' import { queueWatcher } from './scheduler' import Dep, { pushTarget, popTarget } from './dep' import type { SimpleSet } from '../util/index' let uid = 0 /** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ 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)) { dep.addSub(this) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * Subscriber interface. * Will be called when a dependency changes. */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
总结
最后放一张图,辅助理解整个响应原理的流程,后面的文章我会时不时的回头看这张图
发表回复