github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/libs/vue-1.0.24/src/watcher.js (about) 1 import config from './config' 2 import Dep from './observer/dep' 3 import { parseExpression } from './parsers/expression' 4 import { pushWatcher } from './batcher' 5 import { 6 extend, 7 warn, 8 isArray, 9 isObject, 10 nextTick, 11 _Set as Set 12 } from './util/index' 13 14 let uid = 0 15 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 * @param {Vue} vm 22 * @param {String|Function} expOrFn 23 * @param {Function} cb 24 * @param {Object} options 25 * - {Array} filters 26 * - {Boolean} twoWay 27 * - {Boolean} deep 28 * - {Boolean} user 29 * - {Boolean} sync 30 * - {Boolean} lazy 31 * - {Function} [preProcess] 32 * - {Function} [postProcess] 33 * @constructor 34 */ 35 36 export default function Watcher (vm, expOrFn, cb, options) { 37 // mix in options 38 if (options) { 39 extend(this, options) 40 } 41 var isFn = typeof expOrFn === 'function' 42 this.vm = vm 43 vm._watchers.push(this) 44 this.expression = expOrFn 45 this.cb = cb 46 this.id = ++uid // uid for batching 47 this.active = true 48 this.dirty = this.lazy // for lazy watchers 49 this.deps = [] 50 this.newDeps = [] 51 this.depIds = new Set() 52 this.newDepIds = new Set() 53 this.prevError = null // for async error stacks 54 // parse expression for getter/setter 55 if (isFn) { 56 this.getter = expOrFn 57 this.setter = undefined 58 } else { 59 var res = parseExpression(expOrFn, this.twoWay) 60 this.getter = res.get 61 this.setter = res.set 62 } 63 this.value = this.lazy 64 ? undefined 65 : this.get() 66 // state for avoiding false triggers for deep and Array 67 // watchers during vm._digest() 68 this.queued = this.shallow = false 69 } 70 71 /** 72 * Evaluate the getter, and re-collect dependencies. 73 */ 74 75 Watcher.prototype.get = function () { 76 this.beforeGet() 77 var scope = this.scope || this.vm 78 var value 79 try { 80 value = this.getter.call(scope, scope) 81 } catch (e) { 82 if ( 83 process.env.NODE_ENV !== 'production' && 84 config.warnExpressionErrors 85 ) { 86 warn( 87 'Error when evaluating expression ' + 88 '"' + this.expression + '": ' + e.toString(), 89 this.vm 90 ) 91 } 92 } 93 // "touch" every property so they are all tracked as 94 // dependencies for deep watching 95 if (this.deep) { 96 traverse(value) 97 } 98 if (this.preProcess) { 99 value = this.preProcess(value) 100 } 101 if (this.filters) { 102 value = scope._applyFilters(value, null, this.filters, false) 103 } 104 if (this.postProcess) { 105 value = this.postProcess(value) 106 } 107 this.afterGet() 108 return value 109 } 110 111 /** 112 * Set the corresponding value with the setter. 113 * 114 * @param {*} value 115 */ 116 117 Watcher.prototype.set = function (value) { 118 var scope = this.scope || this.vm 119 if (this.filters) { 120 value = scope._applyFilters( 121 value, this.value, this.filters, true) 122 } 123 try { 124 this.setter.call(scope, scope, value) 125 } catch (e) { 126 if ( 127 process.env.NODE_ENV !== 'production' && 128 config.warnExpressionErrors 129 ) { 130 warn( 131 'Error when evaluating setter ' + 132 '"' + this.expression + '": ' + e.toString(), 133 this.vm 134 ) 135 } 136 } 137 // two-way sync for v-for alias 138 var forContext = scope.$forContext 139 if (forContext && forContext.alias === this.expression) { 140 if (forContext.filters) { 141 process.env.NODE_ENV !== 'production' && warn( 142 'It seems you are using two-way binding on ' + 143 'a v-for alias (' + this.expression + '), and the ' + 144 'v-for has filters. This will not work properly. ' + 145 'Either remove the filters or use an array of ' + 146 'objects and bind to object properties instead.', 147 this.vm 148 ) 149 return 150 } 151 forContext._withLock(function () { 152 if (scope.$key) { // original is an object 153 forContext.rawValue[scope.$key] = value 154 } else { 155 forContext.rawValue.$set(scope.$index, value) 156 } 157 }) 158 } 159 } 160 161 /** 162 * Prepare for dependency collection. 163 */ 164 165 Watcher.prototype.beforeGet = function () { 166 Dep.target = this 167 } 168 169 /** 170 * Add a dependency to this directive. 171 * 172 * @param {Dep} dep 173 */ 174 175 Watcher.prototype.addDep = function (dep) { 176 var id = dep.id 177 if (!this.newDepIds.has(id)) { 178 this.newDepIds.add(id) 179 this.newDeps.push(dep) 180 if (!this.depIds.has(id)) { 181 dep.addSub(this) 182 } 183 } 184 } 185 186 /** 187 * Clean up for dependency collection. 188 */ 189 190 Watcher.prototype.afterGet = function () { 191 Dep.target = null 192 var i = this.deps.length 193 while (i--) { 194 var dep = this.deps[i] 195 if (!this.newDepIds.has(dep.id)) { 196 dep.removeSub(this) 197 } 198 } 199 var tmp = this.depIds 200 this.depIds = this.newDepIds 201 this.newDepIds = tmp 202 this.newDepIds.clear() 203 tmp = this.deps 204 this.deps = this.newDeps 205 this.newDeps = tmp 206 this.newDeps.length = 0 207 } 208 209 /** 210 * Subscriber interface. 211 * Will be called when a dependency changes. 212 * 213 * @param {Boolean} shallow 214 */ 215 216 Watcher.prototype.update = function (shallow) { 217 if (this.lazy) { 218 this.dirty = true 219 } else if (this.sync || !config.async) { 220 this.run() 221 } else { 222 // if queued, only overwrite shallow with non-shallow, 223 // but not the other way around. 224 this.shallow = this.queued 225 ? shallow 226 ? this.shallow 227 : false 228 : !!shallow 229 this.queued = true 230 // record before-push error stack in debug mode 231 /* istanbul ignore if */ 232 if (process.env.NODE_ENV !== 'production' && config.debug) { 233 this.prevError = new Error('[vue] async stack trace') 234 } 235 pushWatcher(this) 236 } 237 } 238 239 /** 240 * Batcher job interface. 241 * Will be called by the batcher. 242 */ 243 244 Watcher.prototype.run = function () { 245 if (this.active) { 246 var value = this.get() 247 if ( 248 value !== this.value || 249 // Deep watchers and watchers on Object/Arrays should fire even 250 // when the value is the same, because the value may 251 // have mutated; but only do so if this is a 252 // non-shallow update (caused by a vm digest). 253 ((isObject(value) || this.deep) && !this.shallow) 254 ) { 255 // set new value 256 var oldValue = this.value 257 this.value = value 258 // in debug + async mode, when a watcher callbacks 259 // throws, we also throw the saved before-push error 260 // so the full cross-tick stack trace is available. 261 var prevError = this.prevError 262 /* istanbul ignore if */ 263 if (process.env.NODE_ENV !== 'production' && 264 config.debug && prevError) { 265 this.prevError = null 266 try { 267 this.cb.call(this.vm, value, oldValue) 268 } catch (e) { 269 nextTick(function () { 270 throw prevError 271 }, 0) 272 throw e 273 } 274 } else { 275 this.cb.call(this.vm, value, oldValue) 276 } 277 } 278 this.queued = this.shallow = false 279 } 280 } 281 282 /** 283 * Evaluate the value of the watcher. 284 * This only gets called for lazy watchers. 285 */ 286 287 Watcher.prototype.evaluate = function () { 288 // avoid overwriting another watcher that is being 289 // collected. 290 var current = Dep.target 291 this.value = this.get() 292 this.dirty = false 293 Dep.target = current 294 } 295 296 /** 297 * Depend on all deps collected by this watcher. 298 */ 299 300 Watcher.prototype.depend = function () { 301 var i = this.deps.length 302 while (i--) { 303 this.deps[i].depend() 304 } 305 } 306 307 /** 308 * Remove self from all dependencies' subcriber list. 309 */ 310 311 Watcher.prototype.teardown = function () { 312 if (this.active) { 313 // remove self from vm's watcher list 314 // this is a somewhat expensive operation so we skip it 315 // if the vm is being destroyed or is performing a v-for 316 // re-render (the watcher list is then filtered by v-for). 317 if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) { 318 this.vm._watchers.$remove(this) 319 } 320 var i = this.deps.length 321 while (i--) { 322 this.deps[i].removeSub(this) 323 } 324 this.active = false 325 this.vm = this.cb = this.value = null 326 } 327 } 328 329 /** 330 * Recrusively traverse an object to evoke all converted 331 * getters, so that every nested property inside the object 332 * is collected as a "deep" dependency. 333 * 334 * @param {*} val 335 */ 336 337 const seenObjects = new Set() 338 function traverse (val, seen) { 339 let i, keys 340 if (!seen) { 341 seen = seenObjects 342 seen.clear() 343 } 344 const isA = isArray(val) 345 const isO = isObject(val) 346 if (isA || isO) { 347 if (val.__ob__) { 348 var depId = val.__ob__.dep.id 349 if (seen.has(depId)) { 350 return 351 } else { 352 seen.add(depId) 353 } 354 } 355 if (isA) { 356 i = val.length 357 while (i--) traverse(val[i], seen) 358 } else if (isO) { 359 keys = Object.keys(val) 360 i = keys.length 361 while (i--) traverse(val[keys[i]], seen) 362 } 363 } 364 }