mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-02 12:07:39 +08:00
fix: async edge case fix should apply to more browsers
also optimize fix performance by avoiding calls to performance.now()
This commit is contained in:
parent
ba9907c73c
commit
ba0ebd4771
@ -7,7 +7,8 @@ import { callHook, activateChildComponent } from '../instance/lifecycle'
|
||||
import {
|
||||
warn,
|
||||
nextTick,
|
||||
devtools
|
||||
devtools,
|
||||
inBrowser
|
||||
} from '../util/index'
|
||||
|
||||
export const MAX_UPDATE_COUNT = 100
|
||||
@ -32,10 +33,35 @@ function resetSchedulerState () {
|
||||
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
|
||||
|
||||
let getNow
|
||||
if (inBrowser) {
|
||||
// Determine what event timestamp the browser is using. Annoyingly, the
|
||||
// timestamp can either be hi-res ( relative to poge 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.
|
||||
const lowResNow = Date.now()
|
||||
const eventTimestamp = document.createEvent('Event').timeStamp
|
||||
// the event timestamp is created after Date.now(), if it's smaller
|
||||
// it means it's using a hi-res timestamp.
|
||||
getNow = eventTimestamp < lowResNow
|
||||
? () => performance.now() // hi-res
|
||||
: Date.now // low-res
|
||||
} else {
|
||||
getNow = Date.now
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush both queues and run the watchers.
|
||||
*/
|
||||
function flushSchedulerQueue () {
|
||||
currentFlushTimestamp = getNow()
|
||||
flushing = true
|
||||
let watcher, id
|
||||
|
||||
|
@ -5,6 +5,8 @@ import { noop } from 'shared/util'
|
||||
import { handleError } from './error'
|
||||
import { isIE, isIOS, isNative } from './env'
|
||||
|
||||
export let isUsingMicroTask = false
|
||||
|
||||
const callbacks = []
|
||||
let pending = false
|
||||
|
||||
@ -48,6 +50,7 @@ if (typeof Promise !== 'undefined' && isNative(Promise)) {
|
||||
// "force" the microtask queue to be flushed by adding an empty timer.
|
||||
if (isIOS) setTimeout(noop)
|
||||
}
|
||||
isUsingMicroTask = true
|
||||
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
|
||||
isNative(MutationObserver) ||
|
||||
// PhantomJS and iOS 7.x
|
||||
@ -66,6 +69,7 @@ if (typeof Promise !== 'undefined' && isNative(Promise)) {
|
||||
counter = (counter + 1) % 2
|
||||
textNode.data = String(counter)
|
||||
}
|
||||
isUsingMicroTask = true
|
||||
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
|
||||
// Fallback to setImmediate.
|
||||
// Techinically it leverages the (macro) task queue,
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
import { isDef, isUndef } from 'shared/util'
|
||||
import { updateListeners } from 'core/vdom/helpers/index'
|
||||
import { isIE, isChrome, supportsPassive } from 'core/util/index'
|
||||
import { isIE, supportsPassive, isUsingMicroTask } from 'core/util/index'
|
||||
import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
|
||||
import { currentFlushTimestamp } from 'core/observer/scheduler'
|
||||
|
||||
// normalize v-model event tokens that can only be determined at runtime.
|
||||
// it's important to place the event as the first in the array because
|
||||
@ -44,17 +45,17 @@ function add (
|
||||
capture: boolean,
|
||||
passive: boolean
|
||||
) {
|
||||
if (isChrome) {
|
||||
// async edge case #6566: inner click event triggers patch, event handler
|
||||
// attached to outer element during patch, and triggered again. This only
|
||||
// happens in Chrome as it fires microtask ticks between event propagation.
|
||||
// the solution is simple: we save the timestamp when a handler is attached,
|
||||
// and the handler would only fire if the event passed to it was fired
|
||||
// AFTER it was attached.
|
||||
const now = performance.now()
|
||||
// async edge case #6566: inner click event triggers patch, event handler
|
||||
// attached to outer element during patch, and triggered again. This only
|
||||
// happens in Chrome as it fires microtask ticks between event propagation.
|
||||
// the solution is simple: we save the timestamp when a handler is attached,
|
||||
// and the handler would only fire if the event passed to it was fired
|
||||
// AFTER it was attached.
|
||||
if (isUsingMicroTask) {
|
||||
const attachedTimestamp = currentFlushTimestamp
|
||||
const original = handler
|
||||
handler = original._wrapper = function (e) {
|
||||
if (e.timeStamp >= now) {
|
||||
if (e.timeStamp >= attachedTimestamp) {
|
||||
return original.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user