import type Watcher from'./watcher' import { remove } from'../util/index'
let uid = 0
/** * A dep is an observable that can have multiple * directives subscribing to it. */ exportdefaultclassDep{ static target: ?Watcher; id: number; subs: Array<Watcher>;
// the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. Dep.target = null const targetStack = []
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. */ exportdefaultclassWatcher{ deps: Array<Dep>; newDeps: Array<Dep>; // 此处省略一堆类型声明, 具体在源码查看 // ...
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.computed = !!options.computed this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.computed = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.computed // for computed watchers this.deps = [] this.newDeps = [] this.depIds = newSet() this.newDepIds = newSet() 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 = function () {} 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 ) } } if (this.computed) { this.value = undefined this.dep = new Dep() } else { this.value = this.get() } }
/** * Evaluate the getter, and re-collect dependencies. */ get () { // 入栈 缓存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 if (this.deep) { // 递归对象或数组 触发getter traverse(value) } // 出栈 恢复Watcher 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 } // 后面省略 // ... }
set: functionreactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } // shallow为false时 将新值设置成响应式对象 childOb = !shallow && observe(newVal) // 通知订阅者 dep.notify() }
值发生变化时,会调用dep的notify方法,看下Dep对notify的定义
1 2 3 4 5 6 7 8
// 派发更新 notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
// run 定义在watcher里面 /** * 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) } } } }
const queue: Array<Watcher> = [] const activatedChildren: Array<Component> = [] let has: { [key: number]: ?true } = {} let circular: { [key: number]: number } = {} let waiting = false let flushing = false let index = 0
/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */ exportfunctionqueueWatcher (watcher: Watcher) { const id = watcher.id // 保证每个watcher只会被添加一次 if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true // 同步 if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } // 在下一个tick 异步执行flushSchedulerQueue nextTick(flushSchedulerQueue) } } }
/** * Flush both queues and run the watchers. */ functionflushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id
// Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. // 队列排序 原因如上注释 queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed // as we run existing watchers // 队列遍历 for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } }
// keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue)
// devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } }