UNPKG

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