123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- /* @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
- }
- }
- }
|