• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* eslint-disable */
2import Dep, { pushTarget, popTarget } from './dep';
3import {
4  remove,
5  extend,
6  isObject
7} from '../../utils/index.ts';
8
9let uid = 0;
10
11/**
12 * A watcher parses an expression, collects dependencies,
13 * and fires callback when the expression value changes.
14 * This is used for both the $watch() api and directives.
15 * @param {Vue} vm
16 * @param {String|Function} expOrFn
17 * @param {Function} cb
18 * @param {Object} options
19 *                 - {Array} filters
20 *                 - {Boolean} twoWay
21 *                 - {Boolean} deep
22 *                 - {Boolean} user
23 *                 - {Boolean} sync
24 *                 - {Boolean} lazy
25 *                 - {Function} [preProcess]
26 *                 - {Function} [postProcess]
27 * @constructor
28 */
29
30export default function Watcher (vm, expOrFn, cb, options) {
31  // Mix in options.
32  if (options) {
33    extend(this, options);
34  }
35  const isFn = typeof expOrFn === 'function';
36  this.vm = vm;
37  vm._watchers.push(this);
38  this.expression = expOrFn;
39  this.cb = cb;
40  this.id = ++uid;
41  this.active = true;
42  this.dirty = this.lazy;
43  this.deps = [];
44  this.newDeps = [];
45  this.depIds = new Set();
46  this.newDepIds = new Set();
47
48  if (isFn) {
49    this.getter = expOrFn;
50  }
51  this.value = this.lazy
52    ? undefined
53    : this.get();
54
55  // State for avoiding false triggers for deep and Array watchers during vm._digest().
56  this.queued = this.shallow = false;
57}
58
59/**
60 * Evaluate the getter, and re-collect dependencies.
61 */
62Watcher.prototype.get = function () {
63  pushTarget(this);
64  const value = this.getter.call(this.vm, this.vm);
65
66  // "touch" every property so they are all tracked as dependencies for deep watching.
67  if (this.deep) {
68    traverse(value);
69  }
70  popTarget();
71  this.cleanupDeps();
72  return value;
73}
74
75/**
76 * Add a dependency to this directive.
77 * @param {Dep} dep
78 */
79Watcher.prototype.addDep = function (dep) {
80  const id = dep.id;
81  if (!this.newDepIds.has(id)) {
82    this.newDepIds.add(id);
83    this.newDeps.push(dep);
84    if (!this.depIds.has(id)) {
85      dep.addSub(this);
86    }
87  }
88}
89
90/**
91 * Clean up for dependency collection.
92 */
93Watcher.prototype.cleanupDeps = function () {
94  let i = this.deps.length;
95  while (i--) {
96    const dep = this.deps[i];
97    if (!this.newDepIds.has(dep.id)) {
98      dep.removeSub(this);
99    }
100  }
101  let tmp = this.depIds
102  this.depIds = this.newDepIds
103  this.newDepIds = tmp
104  this.newDepIds.clear()
105  tmp = this.deps
106  this.deps = this.newDeps
107  this.newDeps = tmp
108  this.newDeps.length = 0
109}
110
111/**
112 * Subscriber interface. Will be called when a dependency changes.
113 * @param {Boolean} shallow
114 */
115Watcher.prototype.update = function (shallow) {
116  if (this.lazy) {
117    this.dirty = true;
118  } else {
119    this.run();
120  }
121}
122
123/**
124 * Batcher job interface. Will be called by the batcher.
125 */
126Watcher.prototype.run = function () {
127  if (this.active) {
128    const value = this.get();
129    if (
130      value !== this.value ||
131      ((isObject(value) || this.deep) && !this.shallow)
132    ) {
133      // Set new value.
134      const oldValue = this.value;
135      this.value = value;
136      this.cb.call(this.vm, value, oldValue);
137    }
138    this.queued = this.shallow = false;
139  }
140}
141
142/**
143 * Evaluate the value of the watcher. This only gets called for lazy watchers.
144 */
145Watcher.prototype.evaluate = function () {
146  this.value = this.get();
147  this.dirty = false;
148}
149
150/**
151 * Depend on all deps collected by this watcher.
152 */
153Watcher.prototype.depend = function () {
154  let i = this.deps.length;
155  while (i--) {
156    this.deps[i].depend();
157  }
158}
159
160/**
161 * Remove self from all dependencies' subcriber list.
162 */
163Watcher.prototype.teardown = function () {
164  if (this.active) {
165    /* Remove self from vm's watcher list.
166     * This is a somewhat expensive operation so we skip it
167     * if the vm is being destroyed or is performing a v-for
168     * re-render (the watcher list is then filtered by v-for).
169     */
170    if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) {
171      remove(this.vm._watchers, this);
172    }
173    let i = this.deps.length;
174    while (i--) {
175      this.deps[i].removeSub(this);
176    }
177    this.active = false;
178    this.vm = this.cb = this.value = null;
179  }
180}
181
182/**
183 * <p>Recursively traverse an object to evoke all converted<br>
184 * getters, so that every nested property inside the object<br>
185 * is collected as a "deep" dependency.</p>
186 * @param {*} val
187 * @param {Set} seen
188 */
189const seenObjects = new Set();
190
191function traverse (val, seen) {
192  let i, keys, isA, isO;
193  if (!seen) {
194    seen = seenObjects;
195    seen.clear();
196  }
197  isA = Array.isArray(val);
198  isO = isObject(val);
199  if (isA || isO) {
200    if (val.__ob__) {
201      const depId = val.__ob__.dep.id;
202      if (seen.has(depId)) {
203        return;
204      } else {
205        seen.add(depId);
206      }
207    }
208    if (isA) {
209      i = val.length;
210      while (i--) traverse(val[i], seen);
211    } else if (isO) {
212      keys = Object.keys(val);
213      i = keys.length;
214      while (i--) traverse(val[keys[i]], seen);
215    }
216  }
217}
218