/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */ exportfunctionobserve (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceofVNode) { return } letob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__instanceofObserver) { ob = value.__ob__ } elseif ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = newObserver(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
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 } exportfunctionpushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target }
/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */ exportfunctionqueueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { // 避免watcher重复入队 has[id] = true if (!flushing) { queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true
timerFunc = () => { p.then(flushCallbacks) // In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) }
函数调用栈为空时,执行微任务flushCallbacks:
1 2 3 4 5 6 7 8
functionflushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
/** * Flush both queues and run the watchers. */ functionflushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id
// Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { // 对于renderwatcher有 // 调用before就是触发beforeUpdate的hook //new Watcher(vm, updateComponent, noop, { //before () { //if (vm._isMounted && !vm._isDestroyed) { //callHook(vm, 'beforeUpdate') //} //} //}, true /* isRenderWatcher */) watcher.before() } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } }
// keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue)
// devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } }
/** * 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) { const info = `callback for watcher "${this.expression}"` invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info) } else { this.cb.call(this.vm, value, oldValue) } } } }