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; // 所处在的 Vue 实例
expression: string; // 在非生产环境下该属性的值为表达式(expOrFn)的字符串表示,在生产环境下其值为空字符串
cb: Function; // 被观察者发生变化后的响应回调
id: number; // Watcher 的 id,根据实例化的时机按顺序递增
deep: boolean; // 用来告诉当前观察者实例对象是否是深度观测,在编写 watch 就有此选项,用于决定是否深度观测这个值,默认为 false
user: boolean; // 用来标识当前观察者实例对象是 开发者定义的 还是 内部定义的,前者是 options 中的 watch 和 $watch,后者是渲染函数的观察者、计算属性的观察者等
lazy: boolean; // computed 时用到,用来标识当前观察者实例对象是否是计算属性的观察者
sync: boolean; // 用来告诉观察者当数据变化时是否同步求值并执行回调,一般情况下不会选择同步,一般是选择异步来计算
dirty: boolean; // 当 lazy 为 true 时才会有效,代表当前的值是否是同步的
active: boolean; // 它标识着该观察者实例对象是否是激活状态
deps: Array<Dep>; // 当前 Watcher 所在的 Dep 集合
newDeps: Array<Dep>; // 用于缓存最新依赖收集过程中当前 Watch 所在的 Dep 集合,会与旧的 Dep 集合对比,进行相应的依赖增改,提高性能和一致性,在每次依赖收集结束后会清空
depIds: SimpleSet; // 当前 Watcher 所在的 Dep 的 id 集合
newDepIds: SimpleSet; // 用于缓存最新依赖收集过程中当前 Watch 所在的 Dep 集合,会与旧的 Dep 集合对比,进行相应的依赖增改,提高性能和一致性,在每次依赖收集结束后会清空
before: ?Function; // 可以理解为 Watcher 实例的钩子,当数据变化之后,触发更新之前,调用在创建渲染函数的观察者实例对象时传递的 before 选项
getter: Function; // 触发依赖收集的开启函数,如果是组件的 Watcher,存放的就是渲染函数,如果是 watch 或者 $watch,则存放的是获取监听的值的函数,如果是 computed,则放的是计算函数
value: any; // watch 的值,或者 computed 的结果(可能非最新)
constructor (
vm: Component, // vue 实例
expOrFn: string | Function, // 要观察的表达式或者函数
cb: Function, // 响应的回调函数
options?: ?Object, // 传给当前观察者对象的一些参数
isRenderWatcher?: boolean // 标识该观察者实例是否是渲染函数的观察者
) {
this.vm = vm // 观察者所在的实例
if (isRenderWatcher) {
vm._watcher = this // 如果是作为实例的渲染函数的观察者,则将当前观察者对象赋值到 _watcher
}
vm._watchers.push(this) // 将当前观察者对象添加所在实例的观察者队列里
// 参数初始化
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()
: ''
// expOrFn 初始化,最终都会转成一个 function
// 如果 expOrFn 是函数则不必多说,如果是 string,则返回一个用于取该键值的函数 function(obj) {return obj[keyPath]}
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// 返回取键值的函数,同时支持 'a.b.c',这类键值,但如果 exp 不符合键值的规则,则会返回一个 undefined
this.getter = parsePath(expOrFn)
// 如果 exp 不符合键值的规则,则将 getter 赋一个空函数,同时抛出错误
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
)
}
}
// https://github.com/vuejs/vue/issues/7767 computed
// https://github.com/vuejs/vue/issues/8446 back to lazy
// 如果是惰性的则返回 undefined 反之直接获取一次,获取 exp 对应的值,或者执行以下传进来的 expOrFn
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
//
// 获取对应的值,或者触发对应的函数来获取其中的值来触发依赖收集
get () {
// Dep.target = 当前的 Watcher
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
// 如果是深度的,怎深度遍历 value,触发其深层值的 get,收集依赖
if (this.deep) {
traverse(value)
}
// 将当前的 Watcher 推出 target 队列
popTarget()
// 清楚一次性的 Dep 收集
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
// 触发被监听值的 get 后,触发依赖收集之后会到此处开始收集
addDep (dep: Dep) {
// 获取对应 Dep 集合的id
const id = dep.id
// 如果在本次 get 中收集过了则不再收集,反之收集
if (!this.newDepIds.has(id)) {
// 收集 ID
this.newDepIds.add(id)
// 收集 Dep 实例
this.newDeps.push(dep)
// 相较于 newDepIds,depIds 是真正长期有效的
// newDepIds, 仅在一次 get 中有效
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
// 校准 Dep,使得依赖关系保持一致性
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
// 移除上一次收集到当前 Watcher,但本次没有收集到的 Dep
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.
*/
// Dep.notify 将触发的更新函数
update () {
/* istanbul ignore else */
// 如果获取值的惰性的(computed),则修改值的状态为非最新值
// 初次之外如果是同步更新的条件下,则直接进行对应的响应更新
// 如果是正常的异步更新的条件下,则将当前 Watcher 添加到异步更新的 Watcher 队列中
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 () {
// 如果当前 Watcher 还处于激活状态,才继续
if (this.active) {
// 获取更新后的值,get 会触发依赖收集
// 如果是组件的 Watcher 则会触发渲染更新,返回空,
// 如果是 普通的 watch,则会尝试获取值,返回对应的值
// 如果是 computed,则会进行计算返回对应的值
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
) {
// 设置新的值
const oldValue = this.value
this.value = value
// 执行回调函数
// 如果当前的 Watcher 是又开发者自定义的,比如 $watch, watch, computed,则加上 try catch 来捕获这可能的错误
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 () {
// 如果当前 watcher 实例属于活跃状态才进行取消监听
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.
// 如果当前 vue 实例已经被销毁了则跳过移除,因为移除操作十分消耗性能
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
// 通知对应的 Deps 移除自身
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
// 将当前 watcher 设置为不活跃状态
this.active = false
}
}
}