5569e67231cc483147841cd17550f0c606515e06d5d5930383402e2a063b42e3aabba2c8a9b1e7a0d4bc485dc30bd3f65ffb19f67dba3ecd3d7067b0985835 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. let has: { [key: number]: ?true } = {}
  16. let circular: { [key: number]: number } = {}
  17. let waiting = false
  18. let flushing = false
  19. let index = 0
  20. /**
  21. * Reset the scheduler's state.
  22. */
  23. function resetSchedulerState () {
  24. index = queue.length = activatedChildren.length = 0
  25. has = {}
  26. if (process.env.NODE_ENV !== 'production') {
  27. circular = {}
  28. }
  29. waiting = flushing = false
  30. }
  31. // Async edge case #6566 requires saving the timestamp when event listeners are
  32. // attached. However, calling performance.now() has a perf overhead especially
  33. // if the page has thousands of event listeners. Instead, we take a timestamp
  34. // every time the scheduler flushes and use that for all event listeners
  35. // attached during that flush.
  36. export let currentFlushTimestamp = 0
  37. // Async edge case fix requires storing an event listener's attach timestamp.
  38. let getNow: () => number = Date.now
  39. // Determine what event timestamp the browser is using. Annoyingly, the
  40. // timestamp can either be hi-res (relative to page load) or low-res
  41. // (relative to UNIX epoch), so in order to compare time we have to use the
  42. // same timestamp type when saving the flush timestamp.
  43. // All IE versions use low-res event timestamps, and have problematic clock
  44. // implementations (#9632)
  45. if (inBrowser && !isIE) {
  46. const performance = window.performance
  47. if (
  48. performance &&
  49. typeof performance.now === 'function' &&
  50. getNow() > document.createEvent('Event').timeStamp
  51. ) {
  52. // if the event timestamp, although evaluated AFTER the Date.now(), is
  53. // smaller than it, it means the event is using a hi-res timestamp,
  54. // and we need to use the hi-res version for event listener timestamps as
  55. // well.
  56. getNow = () => performance.now()
  57. }
  58. }
  59. /**
  60. * Flush both queues and run the watchers.
  61. */
  62. function flushSchedulerQueue () {
  63. currentFlushTimestamp = getNow()
  64. flushing = true
  65. let watcher, id
  66. // Sort queue before flush.
  67. // This ensures that:
  68. // 1. Components are updated from parent to child. (because parent is always
  69. // created before the child)
  70. // 2. A component's user watchers are run before its render watcher (because
  71. // user watchers are created before the render watcher)
  72. // 3. If a component is destroyed during a parent component's watcher run,
  73. // its watchers can be skipped.
  74. queue.sort((a, b) => a.id - b.id)
  75. // do not cache length because more watchers might be pushed
  76. // as we run existing watchers
  77. for (index = 0; index < queue.length; index++) {
  78. watcher = queue[index]
  79. if (watcher.before) {
  80. watcher.before()
  81. }
  82. id = watcher.id
  83. has[id] = null
  84. watcher.run()
  85. // in dev build, check and stop circular updates.
  86. if (process.env.NODE_ENV !== 'production' && has[id] != null) {
  87. circular[id] = (circular[id] || 0) + 1
  88. if (circular[id] > MAX_UPDATE_COUNT) {
  89. warn(
  90. 'You may have an infinite update loop ' + (
  91. watcher.user
  92. ? `in watcher with expression "${watcher.expression}"`
  93. : `in a component render function.`
  94. ),
  95. watcher.vm
  96. )
  97. break
  98. }
  99. }
  100. }
  101. // keep copies of post queues before resetting state
  102. const activatedQueue = activatedChildren.slice()
  103. const updatedQueue = queue.slice()
  104. resetSchedulerState()
  105. // call component updated and activated hooks
  106. callActivatedHooks(activatedQueue)
  107. callUpdatedHooks(updatedQueue)
  108. // devtool hook
  109. /* istanbul ignore if */
  110. if (devtools && config.devtools) {
  111. devtools.emit('flush')
  112. }
  113. }
  114. function callUpdatedHooks (queue) {
  115. let i = queue.length
  116. while (i--) {
  117. const watcher = queue[i]
  118. const vm = watcher.vm
  119. if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
  120. callHook(vm, 'updated')
  121. }
  122. }
  123. }
  124. /**
  125. * Queue a kept-alive component that was activated during patch.
  126. * The queue will be processed after the entire tree has been patched.
  127. */
  128. export function queueActivatedComponent (vm: Component) {
  129. // setting _inactive to false here so that a render function can
  130. // rely on checking whether it's in an inactive tree (e.g. router-view)
  131. vm._inactive = false
  132. activatedChildren.push(vm)
  133. }
  134. function callActivatedHooks (queue) {
  135. for (let i = 0; i < queue.length; i++) {
  136. queue[i]._inactive = true
  137. activateChildComponent(queue[i], true /* true */)
  138. }
  139. }
  140. /**
  141. * Push a watcher into the watcher queue.
  142. * Jobs with duplicate IDs will be skipped unless it's
  143. * pushed when the queue is being flushed.
  144. */
  145. export function queueWatcher (watcher: Watcher) {
  146. const id = watcher.id
  147. if (has[id] == null) {
  148. has[id] = true
  149. if (!flushing) {
  150. queue.push(watcher)
  151. } else {
  152. // if already flushing, splice the watcher based on its id
  153. // if already past its id, it will be run next immediately.
  154. let i = queue.length - 1
  155. while (i > index && queue[i].id > watcher.id) {
  156. i--
  157. }
  158. queue.splice(i + 1, 0, watcher)
  159. }
  160. // queue the flush
  161. if (!waiting) {
  162. waiting = true
  163. if (process.env.NODE_ENV !== 'production' && !config.async) {
  164. flushSchedulerQueue()
  165. return
  166. }
  167. nextTick(flushSchedulerQueue)
  168. }
  169. }
  170. }