| /* eslint-disable */ |
| |
| |
| import Dep, { pushTarget, popTarget } from './dep' |
| // import { pushWatcher } from './batcher' |
| import { |
| remove, |
| extend, |
| isObject, |
| createNewSet |
| // _Set as Set |
| } 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. |
| * |
| * @param {Vue} vm |
| * @param {String|Function} expOrFn |
| * @param {Function} cb |
| * @param {Object} options |
| * - {Array} filters |
| * - {Boolean} twoWay |
| * - {Boolean} deep |
| * - {Boolean} user |
| * - {Boolean} sync |
| * - {Boolean} lazy |
| * - {Function} [preProcess] |
| * - {Function} [postProcess] |
| * @constructor |
| */ |
| |
| export default function Watcher (vm, expOrFn, cb, options) { |
| // mix in options |
| if (options) { |
| extend(this, options) |
| } |
| const isFn = typeof expOrFn === 'function' |
| this.vm = vm |
| vm._watchers.push(this) |
| this.expression = expOrFn |
| 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 = createNewSet() // new Set() |
| this.newDepIds = createNewSet() // new Set() |
| // parse expression for getter |
| if (isFn) { |
| this.getter = expOrFn |
| } |
| this.value = this.lazy |
| ? undefined |
| : this.get() |
| // state for avoiding false triggers for deep and Array |
| // watchers during vm._digest() |
| this.queued = this.shallow = false |
| } |
| |
| /** |
| * Evaluate the getter, and re-collect dependencies. |
| */ |
| |
| Watcher.prototype.get = function () { |
| pushTarget(this) |
| const value = this.getter.call(this.vm, this.vm) |
| // "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. |
| * |
| * @param {Dep} dep |
| */ |
| |
| Watcher.prototype.addDep = function (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. |
| */ |
| |
| Watcher.prototype.cleanupDeps = function () { |
| 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. |
| * |
| * @param {Boolean} shallow |
| */ |
| |
| Watcher.prototype.update = function (shallow) { |
| if (this.lazy) { |
| this.dirty = true |
| } else { |
| this.run() |
| } |
| // } else if (this.sync) { |
| // this.run() |
| // } else { |
| // // if queued, only overwrite shallow with non-shallow, |
| // // but not the other way around. |
| // this.shallow = this.queued |
| // ? shallow |
| // ? this.shallow |
| // : false |
| // : !!shallow |
| // this.queued = true |
| // pushWatcher(this) |
| // } |
| } |
| |
| /** |
| * Batcher job interface. |
| * Will be called by the batcher. |
| */ |
| |
| Watcher.prototype.run = function () { |
| 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; but only do so if this is a |
| // non-shallow update (caused by a vm digest). |
| ((isObject(value) || this.deep) && !this.shallow) |
| ) { |
| // set new value |
| const oldValue = this.value |
| this.value = value |
| this.cb.call(this.vm, value, oldValue) |
| } |
| this.queued = this.shallow = false |
| } |
| } |
| |
| /** |
| * Evaluate the value of the watcher. |
| * This only gets called for lazy watchers. |
| */ |
| |
| Watcher.prototype.evaluate = function () { |
| this.value = this.get() |
| this.dirty = false |
| } |
| |
| /** |
| * Depend on all deps collected by this watcher. |
| */ |
| |
| Watcher.prototype.depend = function () { |
| let i = this.deps.length |
| while (i--) { |
| this.deps[i].depend() |
| } |
| } |
| |
| /** |
| * Remove self from all dependencies' subcriber list. |
| */ |
| |
| Watcher.prototype.teardown = function () { |
| 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 or is performing a v-for |
| // re-render (the watcher list is then filtered by v-for). |
| if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) { |
| remove(this.vm._watchers, this) |
| } |
| let i = this.deps.length |
| while (i--) { |
| this.deps[i].removeSub(this) |
| } |
| this.active = false |
| this.vm = this.cb = this.value = null |
| } |
| } |
| |
| /** |
| * Recrusively traverse an object to evoke all converted |
| * getters, so that every nested property inside the object |
| * is collected as a "deep" dependency. |
| * |
| * @param {*} val |
| * @param {Set} seen |
| */ |
| |
| const seenObjects = createNewSet() // new Set() |
| /* istanbul ignore next */ |
| function traverse (val, seen) { |
| let i, keys, isA, isO |
| if (!seen) { |
| seen = seenObjects |
| seen.clear() |
| } |
| isA = Array.isArray(val) |
| isO = isObject(val) |
| if (isA || isO) { |
| if (val.__ob__) { |
| const depId = val.__ob__.dep.id |
| if (seen.has(depId)) { |
| return |
| } else { |
| seen.add(depId) |
| } |
| } |
| if (isA) { |
| i = val.length |
| while (i--) traverse(val[i], seen) |
| } else if (isO) { |
| keys = Object.keys(val) |
| i = keys.length |
| while (i--) traverse(val[keys[i]], seen) |
| } |
| } |
| } |