mirror of
https://github.com/vuejs/vue.git
synced 2024-11-22 04:39:46 +00:00
parent
6658b81a3b
commit
4e0c48511d
@ -1,7 +1,4 @@
|
||||
/* @flow */
|
||||
/* globals MessageChannel */
|
||||
|
||||
import { handleError } from './error'
|
||||
|
||||
// can we use __proto__?
|
||||
export const hasProto = '__proto__' in {}
|
||||
@ -62,88 +59,6 @@ export const hasSymbol =
|
||||
typeof Symbol !== 'undefined' && isNative(Symbol) &&
|
||||
typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)
|
||||
|
||||
/**
|
||||
* Defer a task to execute it asynchronously.
|
||||
*/
|
||||
export const nextTick = (function () {
|
||||
const callbacks = []
|
||||
let pending = false
|
||||
let timerFunc
|
||||
|
||||
function nextTickHandler () {
|
||||
pending = false
|
||||
const copies = callbacks.slice(0)
|
||||
callbacks.length = 0
|
||||
for (let i = 0; i < copies.length; i++) {
|
||||
copies[i]()
|
||||
}
|
||||
}
|
||||
|
||||
// An asynchronous deferring mechanism.
|
||||
// In pre 2.4, we used to use microtasks (Promise/MutationObserver)
|
||||
// but microtasks actually has too high a priority and fires in between
|
||||
// supposedly sequential events (e.g. #4521, #6690) or even between
|
||||
// bubbling of the same event (#6566). Technically setImmediate should be
|
||||
// the ideal choice, but it's not available everywhere; and the only polyfill
|
||||
// that consistently queues the callback after all DOM events triggered in the
|
||||
// same loop is by using MessageChannel.
|
||||
/* istanbul ignore if */
|
||||
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
|
||||
timerFunc = () => {
|
||||
setImmediate(nextTickHandler)
|
||||
}
|
||||
} else if (typeof MessageChannel !== 'undefined' && (
|
||||
isNative(MessageChannel) ||
|
||||
// PhantomJS
|
||||
MessageChannel.toString() === '[object MessageChannelConstructor]'
|
||||
)) {
|
||||
const channel = new MessageChannel()
|
||||
const port = channel.port2
|
||||
channel.port1.onmessage = nextTickHandler
|
||||
timerFunc = () => {
|
||||
port.postMessage(1)
|
||||
}
|
||||
} else
|
||||
/* istanbul ignore next */
|
||||
if (typeof Promise !== 'undefined' && isNative(Promise)) {
|
||||
// use microtask in non-DOM environments, e.g. Weex
|
||||
const p = Promise.resolve()
|
||||
timerFunc = () => {
|
||||
p.then(nextTickHandler)
|
||||
}
|
||||
} else {
|
||||
// fallback to setTimeout
|
||||
timerFunc = () => {
|
||||
setTimeout(nextTickHandler, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return function queueNextTick (cb?: Function, ctx?: Object) {
|
||||
let _resolve
|
||||
callbacks.push(() => {
|
||||
if (cb) {
|
||||
try {
|
||||
cb.call(ctx)
|
||||
} catch (e) {
|
||||
handleError(e, ctx, 'nextTick')
|
||||
}
|
||||
} else if (_resolve) {
|
||||
_resolve(ctx)
|
||||
}
|
||||
})
|
||||
if (!pending) {
|
||||
pending = true
|
||||
timerFunc()
|
||||
}
|
||||
// $flow-disable-line
|
||||
if (!cb && typeof Promise !== 'undefined') {
|
||||
return new Promise((resolve, reject) => {
|
||||
_resolve = resolve
|
||||
})
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
let _Set
|
||||
/* istanbul ignore if */ // $flow-disable-line
|
||||
if (typeof Set !== 'undefined' && isNative(Set)) {
|
||||
|
@ -7,4 +7,5 @@ export * from './options'
|
||||
export * from './debug'
|
||||
export * from './props'
|
||||
export * from './error'
|
||||
export * from './next-tick'
|
||||
export { defineReactive } from '../observer/index'
|
||||
|
116
src/core/util/next-tick.js
Normal file
116
src/core/util/next-tick.js
Normal file
@ -0,0 +1,116 @@
|
||||
/* @flow */
|
||||
/* globals MessageChannel */
|
||||
|
||||
import { noop } from 'shared/util'
|
||||
import { handleError } from './error'
|
||||
import { isIOS, isNative } from './env'
|
||||
|
||||
const callbacks = []
|
||||
let pending = false
|
||||
|
||||
function flushCallbacks () {
|
||||
pending = false
|
||||
const copies = callbacks.slice(0)
|
||||
callbacks.length = 0
|
||||
for (let i = 0; i < copies.length; i++) {
|
||||
copies[i]()
|
||||
}
|
||||
}
|
||||
|
||||
// Here we have async deferring wrappers using both micro and macro tasks.
|
||||
// In < 2.4 we used micro tasks everywhere, but there are some scenarios where
|
||||
// micro tasks have too high a priority and fires in between supposedly
|
||||
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
|
||||
// event (#6566). However, using macro tasks everywhere also has subtle problems
|
||||
// when state is changed right before repaint (e.g. #6813, out-in transitions).
|
||||
// Here we use micro task by default, but expose a way to force macro task when
|
||||
// needed (e.g. in event handlers attached by v-on).
|
||||
let microTimerFunc
|
||||
let macroTimerFunc
|
||||
let useMacroTask = false
|
||||
|
||||
// Determine (macro) Task defer implementation.
|
||||
// Technically setImmediate should be the ideal choice, but it's only available
|
||||
// in IE. The only polyfill that consistently queues the callback after all DOM
|
||||
// events triggered in the same loop is by using MessageChannel.
|
||||
/* istanbul ignore if */
|
||||
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
|
||||
macroTimerFunc = () => {
|
||||
setImmediate(flushCallbacks)
|
||||
}
|
||||
} else if (typeof MessageChannel !== 'undefined' && (
|
||||
isNative(MessageChannel) ||
|
||||
// PhantomJS
|
||||
MessageChannel.toString() === '[object MessageChannelConstructor]'
|
||||
)) {
|
||||
const channel = new MessageChannel()
|
||||
const port = channel.port2
|
||||
channel.port1.onmessage = flushCallbacks
|
||||
macroTimerFunc = () => {
|
||||
port.postMessage(1)
|
||||
}
|
||||
} else {
|
||||
macroTimerFunc = () => {
|
||||
setTimeout(flushCallbacks, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine MicroTask defer implementation.
|
||||
// $flow-disable-line, istanbul ignore next
|
||||
if (typeof Promise !== 'undefined' && isNative(Promise)) {
|
||||
const p = Promise.resolve()
|
||||
microTimerFunc = () => {
|
||||
p.then(flushCallbacks)
|
||||
// in problematic UIWebViews, Promise.then doesn't completely break, but
|
||||
// it can get stuck in a weird state where callbacks are pushed into the
|
||||
// microtask queue but the queue isn't being flushed, until the browser
|
||||
// needs to do some other work, e.g. handle a timer. Therefore we can
|
||||
// "force" the microtask queue to be flushed by adding an empty timer.
|
||||
if (isIOS) setTimeout(noop)
|
||||
}
|
||||
} else {
|
||||
// fallback to macro
|
||||
microTimerFunc = macroTimerFunc
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a function so that if any code inside triggers state change,
|
||||
* the changes are queued using a Task instead of a MicroTask.
|
||||
*/
|
||||
export function withMacroTask (fn: Function): Function {
|
||||
return fn._withTask || (fn._withTask = function () {
|
||||
useMacroTask = true
|
||||
const res = fn.apply(null, arguments)
|
||||
useMacroTask = false
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
export function nextTick (cb?: Function, ctx?: Object): ?Promise {
|
||||
let _resolve
|
||||
callbacks.push(() => {
|
||||
if (cb) {
|
||||
try {
|
||||
cb.call(ctx)
|
||||
} catch (e) {
|
||||
handleError(e, ctx, 'nextTick')
|
||||
}
|
||||
} else if (_resolve) {
|
||||
_resolve(ctx)
|
||||
}
|
||||
})
|
||||
if (!pending) {
|
||||
pending = true
|
||||
if (useMacroTask) {
|
||||
macroTimerFunc()
|
||||
} else {
|
||||
microTimerFunc()
|
||||
}
|
||||
}
|
||||
// $flow-disable-line
|
||||
if (!cb && typeof Promise !== 'undefined') {
|
||||
return new Promise(resolve => {
|
||||
_resolve = resolve
|
||||
})
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
import { isDef, isUndef } from 'shared/util'
|
||||
import { updateListeners } from 'core/vdom/helpers/index'
|
||||
import { isIE, supportsPassive } from 'core/util/env'
|
||||
import { withMacroTask, isIE, supportsPassive } from 'core/util/index'
|
||||
import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
|
||||
|
||||
// normalize v-model event tokens that can only be determined at runtime.
|
||||
@ -28,6 +28,16 @@ function normalizeEvents (on) {
|
||||
|
||||
let target: HTMLElement
|
||||
|
||||
function createOnceHandler (handler, event, capture) {
|
||||
const _target = target // save current target element in closure
|
||||
return function onceHandler () {
|
||||
const res = handler.apply(null, arguments)
|
||||
if (res !== null) {
|
||||
remove(event, onceHandler, capture, _target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function add (
|
||||
event: string,
|
||||
handler: Function,
|
||||
@ -35,18 +45,8 @@ function add (
|
||||
capture: boolean,
|
||||
passive: boolean
|
||||
) {
|
||||
if (once) {
|
||||
const oldHandler = handler
|
||||
const _target = target // save current target element in closure
|
||||
handler = function (ev) {
|
||||
const res = arguments.length === 1
|
||||
? oldHandler(ev)
|
||||
: oldHandler.apply(null, arguments)
|
||||
if (res !== null) {
|
||||
remove(event, handler, capture, _target)
|
||||
}
|
||||
}
|
||||
}
|
||||
handler = withMacroTask(handler)
|
||||
if (once) handler = createOnceHandler(handler, event, capture)
|
||||
target.addEventListener(
|
||||
event,
|
||||
handler,
|
||||
@ -62,7 +62,11 @@ function remove (
|
||||
capture: boolean,
|
||||
_target?: HTMLElement
|
||||
) {
|
||||
(_target || target).removeEventListener(event, handler, capture)
|
||||
(_target || target).removeEventListener(
|
||||
event,
|
||||
handler._withTask || handler,
|
||||
capture
|
||||
)
|
||||
}
|
||||
|
||||
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
<!-- #6566 click event bubbling -->
|
||||
<div id="case-2">
|
||||
<div v-if="expand">
|
||||
<div class="panel" v-if="expand">
|
||||
<button @click="expand = false, countA++">Expand is True</button>
|
||||
</div>
|
||||
<div class="header" v-if="!expand" @click="expand = true, countB++">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { nextTick } from 'core/util/env'
|
||||
import { nextTick } from 'core/util/next-tick'
|
||||
|
||||
describe('nextTick', () => {
|
||||
it('accepts a callback', done => {
|
||||
|
Loading…
Reference in New Issue
Block a user