48b9f6cbe9dba907ff1fb9a1f65da9300c93e4712629ad6862f1001c6da767a59f91bb0a33a4134942bacf5b0d038f4f4050ce73193149c46ace24a1fb5fc7 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /* @flow */
  2. import {
  3. warn,
  4. remove,
  5. isObject,
  6. parsePath,
  7. _Set as Set,
  8. handleError,
  9. noop
  10. } from '../util/index'
  11. import { traverse } from './traverse'
  12. import { queueWatcher } from './scheduler'
  13. import Dep, { pushTarget, popTarget } from './dep'
  14. import type { SimpleSet } from '../util/index'
  15. let uid = 0
  16. /**
  17. * A watcher parses an expression, collects dependencies,
  18. * and fires callback when the expression value changes.
  19. * This is used for both the $watch() api and directives.
  20. */
  21. export default class Watcher {
  22. vm: Component;
  23. expression: string;
  24. cb: Function;
  25. id: number;
  26. deep: boolean;
  27. user: boolean;
  28. lazy: boolean;
  29. sync: boolean;
  30. dirty: boolean;
  31. active: boolean;
  32. deps: Array<Dep>;
  33. newDeps: Array<Dep>;
  34. depIds: SimpleSet;
  35. newDepIds: SimpleSet;
  36. before: ?Function;
  37. getter: Function;
  38. value: any;
  39. constructor (
  40. vm: Component,
  41. expOrFn: string | Function,
  42. cb: Function,
  43. options?: ?Object,
  44. isRenderWatcher?: boolean
  45. ) {
  46. this.vm = vm
  47. if (isRenderWatcher) {
  48. vm._watcher = this
  49. }
  50. vm._watchers.push(this)
  51. // options
  52. if (options) {
  53. this.deep = !!options.deep
  54. this.user = !!options.user
  55. this.lazy = !!options.lazy
  56. this.sync = !!options.sync
  57. this.before = options.before
  58. } else {
  59. this.deep = this.user = this.lazy = this.sync = false
  60. }
  61. this.cb = cb
  62. this.id = ++uid // uid for batching
  63. this.active = true
  64. this.dirty = this.lazy // for lazy watchers
  65. this.deps = []
  66. this.newDeps = []
  67. this.depIds = new Set()
  68. this.newDepIds = new Set()
  69. this.expression = process.env.NODE_ENV !== 'production'
  70. ? expOrFn.toString()
  71. : ''
  72. // parse expression for getter
  73. if (typeof expOrFn === 'function') {
  74. this.getter = expOrFn
  75. } else {
  76. this.getter = parsePath(expOrFn)
  77. if (!this.getter) {
  78. this.getter = noop
  79. process.env.NODE_ENV !== 'production' && warn(
  80. `Failed watching path: "${expOrFn}" ` +
  81. 'Watcher only accepts simple dot-delimited paths. ' +
  82. 'For full control, use a function instead.',
  83. vm
  84. )
  85. }
  86. }
  87. this.value = this.lazy
  88. ? undefined
  89. : this.get()
  90. }
  91. /**
  92. * Evaluate the getter, and re-collect dependencies.
  93. */
  94. get () {
  95. pushTarget(this)
  96. let value
  97. const vm = this.vm
  98. try {
  99. value = this.getter.call(vm, vm)
  100. } catch (e) {
  101. if (this.user) {
  102. handleError(e, vm, `getter for watcher "${this.expression}"`)
  103. } else {
  104. throw e
  105. }
  106. } finally {
  107. // "touch" every property so they are all tracked as
  108. // dependencies for deep watching
  109. if (this.deep) {
  110. traverse(value)
  111. }
  112. popTarget()
  113. this.cleanupDeps()
  114. }
  115. return value
  116. }
  117. /**
  118. * Add a dependency to this directive.
  119. */
  120. addDep (dep: Dep) {
  121. const id = dep.id
  122. if (!this.newDepIds.has(id)) {
  123. this.newDepIds.add(id)
  124. this.newDeps.push(dep)
  125. if (!this.depIds.has(id)) {
  126. dep.addSub(this)
  127. }
  128. }
  129. }
  130. /**
  131. * Clean up for dependency collection.
  132. */
  133. cleanupDeps () {
  134. let i = this.deps.length
  135. while (i--) {
  136. const dep = this.deps[i]
  137. if (!this.newDepIds.has(dep.id)) {
  138. dep.removeSub(this)
  139. }
  140. }
  141. let tmp = this.depIds
  142. this.depIds = this.newDepIds
  143. this.newDepIds = tmp
  144. this.newDepIds.clear()
  145. tmp = this.deps
  146. this.deps = this.newDeps
  147. this.newDeps = tmp
  148. this.newDeps.length = 0
  149. }
  150. /**
  151. * Subscriber interface.
  152. * Will be called when a dependency changes.
  153. */
  154. update () {
  155. /* istanbul ignore else */
  156. if (this.lazy) {
  157. this.dirty = true
  158. } else if (this.sync) {
  159. this.run()
  160. } else {
  161. queueWatcher(this)
  162. }
  163. }
  164. /**
  165. * Scheduler job interface.
  166. * Will be called by the scheduler.
  167. */
  168. run () {
  169. if (this.active) {
  170. const value = this.get()
  171. if (
  172. value !== this.value ||
  173. // Deep watchers and watchers on Object/Arrays should fire even
  174. // when the value is the same, because the value may
  175. // have mutated.
  176. isObject(value) ||
  177. this.deep
  178. ) {
  179. // set new value
  180. const oldValue = this.value
  181. this.value = value
  182. if (this.user) {
  183. try {
  184. this.cb.call(this.vm, value, oldValue)
  185. } catch (e) {
  186. handleError(e, this.vm, `callback for watcher "${this.expression}"`)
  187. }
  188. } else {
  189. this.cb.call(this.vm, value, oldValue)
  190. }
  191. }
  192. }
  193. }
  194. /**
  195. * Evaluate the value of the watcher.
  196. * This only gets called for lazy watchers.
  197. */
  198. evaluate () {
  199. this.value = this.get()
  200. this.dirty = false
  201. }
  202. /**
  203. * Depend on all deps collected by this watcher.
  204. */
  205. depend () {
  206. let i = this.deps.length
  207. while (i--) {
  208. this.deps[i].depend()
  209. }
  210. }
  211. /**
  212. * Remove self from all dependencies' subscriber list.
  213. */
  214. teardown () {
  215. if (this.active) {
  216. // remove self from vm's watcher list
  217. // this is a somewhat expensive operation so we skip it
  218. // if the vm is being destroyed.
  219. if (!this.vm._isBeingDestroyed) {
  220. remove(this.vm._watchers, this)
  221. }
  222. let i = this.deps.length
  223. while (i--) {
  224. this.deps[i].removeSub(this)
  225. }
  226. this.active = false
  227. }
  228. }
  229. }