The JSVAScript event loop mechanism is the core principle of nextTick, and it is strongly recommended to read the following article carefully to see the code below.
Vue3’s nextTick implementation will be updated later in this article
Understand the loop mechanism between EventLoop and stack and host environment
VUE 2.x ntxtTick implementation
In the browser environment
Common macrotasks are
MessageChannel
setTimeout
postMessage
setImmediate
Common microtasks are
MutationObsever
Promise.then
A separate nextTick implementation file SRC /core/util/next-tick.js is maintained for Vue2.5 +. The SRC directory holds the Vue source code implementation. Here is the directory organization of the core code in the SRC directory.
compiler
: compiler code that generates the render functioncore
: Core code, the platform-independent coreplatforms
: platform support, platform specific code, related entry filesserve
: Server renderingsfc
: Single file system implementationshard
: Shared code, common code throughout the code base
Let’s seenextTick
The source of
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 microtasks and (macro) tasks.
// In < 2.4 we use microtasks everywhere, but there are some scenarios where
// microtasks have too high a priority and fire 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 microtask 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 (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
macroTimerFunc = () = > {
setImmediate(flushCallbacks)
}
} else if (typeofMessageChannel ! = ='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 {
/* istanbul ignore next */
macroTimerFunc = () = > {
setTimeout(flushCallbacks, 0)}}// Determine microtask defer implementation.
/* istanbul ignore next, $flow-disable-line */
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 (macro) 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) {
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
})
}
}
Copy the code
Here, Vue also declares
microTimerFunc
(Micro-task realization)macroTimerFunc
(Macro task implementation)useMacroTask
(Whether to use the macro task flag bit)
From the top down, we first look at the code logic that implements the macro task
- First determine if there is one
setImmediate
You can use - And determine if there is one
MessageChannel
You can use - Otherwise downgrade to use
setTimeout
What you can see here is the priority of how the macro tasks are implemented, and how Vue determines whether the methods implemented in those ways are native implementations. Avoid inconsistent behavior from third party profill.
Next comes the microTimerFunc microtask implementation. As you can see, Vue determines if there is a native Promise implementation, if there is a Promise. Resolve, if not, points to the implementation of the macro task.
Here Vue exports two functions
nextTick
withMacroTask
nextTick
In this case, the method of closure is used to cache the callback functions passed in by nextTick for multiple execution in the same tick, and press these callback functions into the same synchronization task for one execution in the nextTick, ensuring the accuracy of multiple execution. At the same time, according to the status, identification bits use the corresponding implementation mode.
withMacroTask
The exported withMacroTask is actually a decorator function that wraps the passed function into a new function, returns the result of the original function execution, sets useMacroTask to true at function execution time, so that the nextTick at execution time forces the macro task implementation to be removed.
The Decorator Pattern allows you to add new functionality to an existing object without changing its structure. This type of design pattern is a structural pattern that acts as a wrapper around existing classes.
This pattern creates a decorator class that wraps the original class and provides additional functionality while preserving the integrity of the class method signature.