123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- /* @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 = 100
- const queue: Array<Watcher> = []
- const activatedChildren: Array<Component> = []
- let has: { [key: number]: ?true } = {}
- let circular: { [key: number]: number } = {}
- let waiting = false
- let flushing = false
- let index = 0
- /**
- * 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.
- 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)
- 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.
- getNow = () => performance.now()
- }
- }
- /**
- * Flush both queues and run the 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.
- queue.sort((a, b) => a.id - b.id)
- // do not cache length because more watchers might be pushed
- // as we run existing watchers
- for (index = 0; index < queue.length; index++) {
- watcher = queue[index]
- if (watcher.before) {
- watcher.before()
- }
- id = watcher.id
- has[id] = null
- watcher.run()
- // in dev build, check and stop circular updates.
- 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
- const activatedQueue = activatedChildren.slice()
- const updatedQueue = queue.slice()
- resetSchedulerState()
- // call component updated and activated hooks
- callActivatedHooks(activatedQueue)
- callUpdatedHooks(updatedQueue)
- // devtool hook
- /* istanbul ignore if */
- if (devtools && config.devtools) {
- devtools.emit('flush')
- }
- }
- function callUpdatedHooks (queue) {
- let i = queue.length
- while (i--) {
- const watcher = queue[i]
- const vm = watcher.vm
- 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.
- */
- 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)
- }
- function callActivatedHooks (queue) {
- for (let i = 0; i < queue.length; i++) {
- queue[i]._inactive = true
- 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.
- */
- export function queueWatcher (watcher: Watcher) {
- const id = watcher.id
- if (has[id] == null) {
- has[id] = true
- if (!flushing) {
- 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.
- 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
- if (process.env.NODE_ENV !== 'production' && !config.async) {
- flushSchedulerQueue()
- return
- }
- nextTick(flushSchedulerQueue)
- }
- }
- }
|