重点关注
flushSchedulerQueue
queueWatcher
/* @flow */import type Watcher from './watcher'import config from '../config'import { callHook, activateChildComponent } from '../instance/lifecycle'import { warn, nextTick, devtools, inBrowser, isIE} from '../util/index'export const MAX_UPDATE_COUNT = 100const queue: Array<Watcher> = [] //记录观察者队列的数组const activatedChildren: Array<Component> = [] //记录活跃的子组件/*一个哈希表,用来存放watcher对象的id,防止重复的watcher对象多次加入*/let has: { [key: number]: ?true } = {}let circular: { [key: number]: number } = {} //持续循环更新的次数,如果超过100次 则判断已经进入了死循环,则会报错let waiting = false //观察者在更新数据时候 等待的标志let flushing = false //进入flushSchedulerQueue 函数等待标志let index = 0 //queue 观察者队列的索引/** * Reset the scheduler's state. *//*重置调度者的状态*/function resetSchedulerState () { index = queue.length = activatedChildren.length = 0 has = {} if (process.env.NODE_ENV !== 'production') { circular = {} } waiting = flushing = false}// Async edge case #6566 requires saving the timestamp when event listeners are// attached. However, calling performance.now() has a perf overhead especially// if the page has thousands of event listeners. Instead, we take a timestamp// every time the scheduler flushes and use that for all event listeners// attached during that flush./** 异步边缘事件#6566需要在连接事件侦听器时保存时间戳。* 但是,调用performance.now()会产生性能开销,特别是在页面具有数千个事件侦听器的情况下。* 取而代之的是,每次调度程序刷新时,我们都会使用一个时间戳,* 并将其用于该刷新期间附加的所有事件侦听器。* */export let currentFlushTimestamp = 0// Async edge case fix requires storing an event listener's attach timestamp.// 获取时间戳let getNow: () => number = Date.now// Determine what event timestamp the browser is using. Annoyingly, the// timestamp can either be hi-res (relative to page load) or low-res// (relative to UNIX epoch), so in order to compare time we have to use the// same timestamp type when saving the flush timestamp.// All IE versions use low-res event timestamps, and have problematic clock// implementations (#9632)//确定浏览器使用的是什么事件时间戳。// 恼人的是,时间戳可以是hi-res(相对于页面加载)或low-res(相对于UNIX epoch),// 所以为了比较时间,我们在保存刷新时间戳时必须使用相同的时间戳类型。// 所有IE版本都使用low-res的事件时间戳,并且有问题的时钟实现(#9632)if (inBrowser && !isIE) { const performance = window.performance if ( performance && typeof performance.now === 'function' && getNow() > document.createEvent('Event').timeStamp ) { // if the event timestamp, although evaluated AFTER the Date.now(), is // smaller than it, it means the event is using a hi-res timestamp, // and we need to use the hi-res version for event listener timestamps as // well. /* 如果事件的时间戳虽然是在Date.now()之后评估的, 但比它小,这意味着事件使用的是hi-res时间戳, 我们需要对事件监听器的时间戳也使用hi-res版本。 * */ getNow = () => performance.now() }}/** * Flush both queues and run the watchers. * 清空队列和运行watchers */function flushSchedulerQueue () { 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. /* 在清空前对队列进行排序,这样做可以保证: 1.组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。 2.一个组件的user watchers比render watcher先运行, 因为user watchers往往比render watcher更早创建 3.如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过。 */ queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers // 这里不用index = queue.length;index > 0; index--的方式写 // 是因为不要将length进行缓存, // 因为在执行处理现有watcher对象期间, // 更多的watcher对象可能会被push进queue for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id /*将has的标记删除*/ has[id] = null /*执行watcher*/ watcher.run() // in dev build, check and stop circular updates. /* 在测试环境中,检测watch是否在死循环中 比如这样一种情况 watch: { test () { this.test++; } } 持续执行了一百次watch代表可能存在死循环 */ 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 /*在重置状态之前保留post队列的副本*/ const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks /*使子组件状态都改编成active同时调用activated钩子*/ callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') }}/*调用updated钩子*/function callUpdatedHooks (queue) { let i = queue.length while (i--) { const watcher = queue[i] const vm = watcher.vm //获取到虚拟dom //判断watcher与vm._watcher 相等 _isMounted已经更新触发了 mounted 钩子函数 if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) { callHook(vm, 'updated') } }}/** * Queue a kept-alive component that was activated during patch. * The queue will be processed after the entire tree has been patched. *//* 在patch期间被激活(activated)的keep-alive组件保存在队列中, 队列将在整个树被patch之后处理*/export function queueActivatedComponent (vm: Component) { // setting _inactive to false here so that a render function can // rely on checking whether it's in an inactive tree (e.g. router-view) vm._inactive = false activatedChildren.push(vm)}/*使子组件状态都改编成active同时调用activated钩子*/function callActivatedHooks (queue) { for (let i = 0; i < queue.length; i++) { queue[i]._inactive = true //判断是否有不活跃的组件 禁用他 如果有活跃组件则触发钩子函数activated activateChildComponent(queue[i], true /* true */) }}/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. *//* 将一个观察者对象push进观察者队列, 在队列中已经存在相同的id则该观察者对象将被跳过, 除非它是在队列被刷新时推送*/export function queueWatcher (watcher: Watcher) { /*获取watcher的id*/ const id = watcher.id /*检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验*/ if (has[id] == null) { has[id] = true if (!flushing) { /*如果队列没有处于清空状态,直接push到队列中即可*/ 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. // 如果队列正在清空,根据watcher的id大小插入到队列 // 如果watcher的id是最小的,将会排在队列的第一个,将会立即执行 /* * 在遍历的时候每次都会对 queue.length 求值, * 因为在 watcher.run() 的时候,很可能用户会再次添加新的watcher, * 也就是会再次执行queueWatcher方法 * */ 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 /* 异步执行更新。目的是由Vue Test Utils使用。 如果设置为 "false",将大大降低性能。 */ if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } // 异步执行watcher队列 nextTick(flushSchedulerQueue) } }}/* 将watcher插入队列的方法 id重复的会跳过,id较小的会被按照顺序排列到最前 保证id最小的watcher最先执行更新 每次插入watcher就会去清空队列,为了避免反复执行清空 使用waiting作为是否在清空队列的标记 flushing也是表示正在清空队列 当清空队列时,也可以插入watcher,但是要排序 不在清空状态时,就直接push* */