blob: cfd637037f8f303e4f7502073bef36248f04ada0 [file] [log] [blame]
/* 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)
}
}
}