UNPKG

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