重点关注

flushSchedulerQueue

queueWatcher

  1. /* @flow */
  2. import type Watcher from './watcher'
  3. import config from '../config'
  4. import { callHook, activateChildComponent } from '../instance/lifecycle'
  5. import {
  6. warn,
  7. nextTick,
  8. devtools,
  9. inBrowser,
  10. isIE
  11. } from '../util/index'
  12. export const MAX_UPDATE_COUNT = 100
  13. const queue: Array<Watcher> = [] //记录观察者队列的数组
  14. const activatedChildren: Array<Component> = [] //记录活跃的子组件
  15. /*一个哈希表,用来存放watcher对象的id,防止重复的watcher对象多次加入*/
  16. let has: { [key: number]: ?true } = {}
  17. let circular: { [key: number]: number } = {} //持续循环更新的次数,如果超过100次 则判断已经进入了死循环,则会报错
  18. let waiting = false //观察者在更新数据时候 等待的标志
  19. let flushing = false //进入flushSchedulerQueue 函数等待标志
  20. let index = 0 //queue 观察者队列的索引
  21. /**
  22. * Reset the scheduler's state.
  23. */
  24. /*重置调度者的状态*/
  25. function resetSchedulerState () {
  26. index = queue.length = activatedChildren.length = 0
  27. has = {}
  28. if (process.env.NODE_ENV !== 'production') {
  29. circular = {}
  30. }
  31. waiting = flushing = false
  32. }
  33. // Async edge case #6566 requires saving the timestamp when event listeners are
  34. // attached. However, calling performance.now() has a perf overhead especially
  35. // if the page has thousands of event listeners. Instead, we take a timestamp
  36. // every time the scheduler flushes and use that for all event listeners
  37. // attached during that flush.
  38. /*
  39. * 异步边缘事件#6566需要在连接事件侦听器时保存时间戳。
  40. * 但是,调用performance.now()会产生性能开销,特别是在页面具有数千个事件侦听器的情况下。
  41. * 取而代之的是,每次调度程序刷新时,我们都会使用一个时间戳,
  42. * 并将其用于该刷新期间附加的所有事件侦听器。
  43. * */
  44. export let currentFlushTimestamp = 0
  45. // Async edge case fix requires storing an event listener's attach timestamp.
  46. // 获取时间戳
  47. let getNow: () => number = Date.now
  48. // Determine what event timestamp the browser is using. Annoyingly, the
  49. // timestamp can either be hi-res (relative to page load) or low-res
  50. // (relative to UNIX epoch), so in order to compare time we have to use the
  51. // same timestamp type when saving the flush timestamp.
  52. // All IE versions use low-res event timestamps, and have problematic clock
  53. // implementations (#9632)
  54. //确定浏览器使用的是什么事件时间戳。
  55. // 恼人的是,时间戳可以是hi-res(相对于页面加载)或low-res(相对于UNIX epoch),
  56. // 所以为了比较时间,我们在保存刷新时间戳时必须使用相同的时间戳类型。
  57. // 所有IE版本都使用low-res的事件时间戳,并且有问题的时钟实现(#9632)
  58. if (inBrowser && !isIE) {
  59. const performance = window.performance
  60. if (
  61. performance &&
  62. typeof performance.now === 'function' &&
  63. getNow() > document.createEvent('Event').timeStamp
  64. ) {
  65. // if the event timestamp, although evaluated AFTER the Date.now(), is
  66. // smaller than it, it means the event is using a hi-res timestamp,
  67. // and we need to use the hi-res version for event listener timestamps as
  68. // well.
  69. /*
  70. 如果事件的时间戳虽然是在Date.now()之后评估的,
  71. 但比它小,这意味着事件使用的是hi-res时间戳,
  72. 我们需要对事件监听器的时间戳也使用hi-res版本。
  73. * */
  74. getNow = () => performance.now()
  75. }
  76. }
  77. /**
  78. * Flush both queues and run the watchers.
  79. * 清空队列和运行watchers
  80. */
  81. function flushSchedulerQueue () {
  82. currentFlushTimestamp = getNow()
  83. flushing = true
  84. let watcher, id
  85. // Sort queue before flush.
  86. // This ensures that:
  87. // 1. Components are updated from parent to child. (because parent is always
  88. // created before the child)
  89. // 2. A component's user watchers are run before its render watcher (because
  90. // user watchers are created before the render watcher)
  91. // 3. If a component is destroyed during a parent component's watcher run,
  92. // its watchers can be skipped.
  93. /*
  94. 在清空前对队列进行排序,这样做可以保证:
  95. 1.组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。
  96. 2.一个组件的user watchers比render watcher先运行,
  97. 因为user watchers往往比render watcher更早创建
  98. 3.如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过。
  99. */
  100. queue.sort((a, b) => a.id - b.id)
  101. // do not cache length because more watchers might be pushed
  102. // as we run existing watchers
  103. // 这里不用index = queue.length;index > 0; index--的方式写
  104. // 是因为不要将length进行缓存,
  105. // 因为在执行处理现有watcher对象期间,
  106. // 更多的watcher对象可能会被push进queue
  107. for (index = 0; index < queue.length; index++) {
  108. watcher = queue[index]
  109. if (watcher.before) {
  110. watcher.before()
  111. }
  112. id = watcher.id
  113. /*将has的标记删除*/
  114. has[id] = null
  115. /*执行watcher*/
  116. watcher.run()
  117. // in dev build, check and stop circular updates.
  118. /*
  119. 在测试环境中,检测watch是否在死循环中
  120. 比如这样一种情况
  121. watch: {
  122. test () {
  123. this.test++;
  124. }
  125. }
  126. 持续执行了一百次watch代表可能存在死循环
  127. */
  128. if (process.env.NODE_ENV !== 'production' && has[id] != null) {
  129. circular[id] = (circular[id] || 0) + 1
  130. if (circular[id] > MAX_UPDATE_COUNT) {
  131. warn(
  132. 'You may have an infinite update loop ' + (
  133. watcher.user
  134. ? `in watcher with expression "${watcher.expression}"`
  135. : `in a component render function.`
  136. ),
  137. watcher.vm
  138. )
  139. break
  140. }
  141. }
  142. }
  143. // keep copies of post queues before resetting state
  144. /*在重置状态之前保留post队列的副本*/
  145. const activatedQueue = activatedChildren.slice()
  146. const updatedQueue = queue.slice()
  147. resetSchedulerState()
  148. // call component updated and activated hooks
  149. /*使子组件状态都改编成active同时调用activated钩子*/
  150. callActivatedHooks(activatedQueue)
  151. callUpdatedHooks(updatedQueue)
  152. // devtool hook
  153. /* istanbul ignore if */
  154. if (devtools && config.devtools) {
  155. devtools.emit('flush')
  156. }
  157. }
  158. /*调用updated钩子*/
  159. function callUpdatedHooks (queue) {
  160. let i = queue.length
  161. while (i--) {
  162. const watcher = queue[i]
  163. const vm = watcher.vm //获取到虚拟dom
  164. //判断watcher与vm._watcher 相等 _isMounted已经更新触发了 mounted 钩子函数
  165. if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
  166. callHook(vm, 'updated')
  167. }
  168. }
  169. }
  170. /**
  171. * Queue a kept-alive component that was activated during patch.
  172. * The queue will be processed after the entire tree has been patched.
  173. */
  174. /*
  175. 在patch期间被激活(activated)的keep-alive组件保存在队列中,
  176. 队列将在整个树被patch之后处理
  177. */
  178. export function queueActivatedComponent (vm: Component) {
  179. // setting _inactive to false here so that a render function can
  180. // rely on checking whether it's in an inactive tree (e.g. router-view)
  181. vm._inactive = false
  182. activatedChildren.push(vm)
  183. }
  184. /*使子组件状态都改编成active同时调用activated钩子*/
  185. function callActivatedHooks (queue) {
  186. for (let i = 0; i < queue.length; i++) {
  187. queue[i]._inactive = true
  188. //判断是否有不活跃的组件 禁用他 如果有活跃组件则触发钩子函数activated
  189. activateChildComponent(queue[i], true /* true */)
  190. }
  191. }
  192. /**
  193. * Push a watcher into the watcher queue.
  194. * Jobs with duplicate IDs will be skipped unless it's
  195. * pushed when the queue is being flushed.
  196. */
  197. /*
  198. 将一个观察者对象push进观察者队列,
  199. 在队列中已经存在相同的id则该观察者对象将被跳过,
  200. 除非它是在队列被刷新时推送
  201. */
  202. export function queueWatcher (watcher: Watcher) {
  203. /*获取watcher的id*/
  204. const id = watcher.id
  205. /*检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验*/
  206. if (has[id] == null) {
  207. has[id] = true
  208. if (!flushing) {
  209. /*如果队列没有处于清空状态,直接push到队列中即可*/
  210. queue.push(watcher)
  211. } else {
  212. // if already flushing, splice the watcher based on its id
  213. // if already past its id, it will be run next immediately.
  214. // 如果队列正在清空,根据watcher的id大小插入到队列
  215. // 如果watcher的id是最小的,将会排在队列的第一个,将会立即执行
  216. /*
  217. * 在遍历的时候每次都会对 queue.length 求值,
  218. * 因为在 watcher.run() 的时候,很可能用户会再次添加新的watcher,
  219. * 也就是会再次执行queueWatcher方法
  220. * */
  221. let i = queue.length - 1
  222. while (i > index && queue[i].id > watcher.id) {
  223. i--
  224. }
  225. queue.splice(i + 1, 0, watcher)
  226. }
  227. // queue the flush
  228. if (!waiting) {
  229. // 如果没有重置队列,就不执行
  230. waiting = true
  231. /*
  232. 异步执行更新。目的是由Vue Test Utils使用。
  233. 如果设置为 "false",将大大降低性能。
  234. */
  235. if (process.env.NODE_ENV !== 'production' && !config.async) {
  236. flushSchedulerQueue()
  237. return
  238. }
  239. // 异步执行watcher队列
  240. nextTick(flushSchedulerQueue)
  241. }
  242. }
  243. }
  244. /*
  245. 将watcher插入队列的方法
  246. id重复的会跳过,id较小的会被按照顺序排列到最前
  247. 保证id最小的watcher最先执行更新
  248. 每次插入watcher就会去清空队列,为了避免反复执行清空
  249. 使用waiting作为是否在清空队列的标记
  250. flushing也是表示正在清空队列
  251. 当清空队列时,也可以插入watcher,但是要排序
  252. 不在清空状态时,就直接push
  253. * */