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