Tips: This is the first technical article, the length is relatively short, mainly in the form of text and key code expression, hope to help you. (Please correct me if I am not correct)

NextTick Functions and usage

Usage: nextTick accepts as an argument a callback function that is intended to defer execution until after the next DOM update. If no callback argument is provided and in an environment that supports Promises, nextTick will return a Promise.
Application scenario: During development, developers need to do something with the new DOM after updating the data. We can’t do anything with the new DOM because we haven’t re-rendered it yet, and that’s where nextTick comes in.

Implementation principle of nextTick

Next, we introduce the working principle of nextTick:

The first thing you should know is that after updating the data (state), DOM updates do not happen synchronously, but asynchronously. There is a queue in vue.js to which Watcher is pushed whenever rendering is needed, and Watcher triggers the rendering process in the next event loop. Here we might have two questions:
**1. Why are DOM updates asynchronous? **
We know that since Vue2.0, the virtual DOM has been used for rendering. The change detection is only sent to the component level, and the components are locally rendered through the diff (alignment) of the virtual DOM. Does the component render twice if it receives two notifications in the same event loop? The fact is that the event loop component only renders once after all the state changes have been made.
**2. What is an event loop? **
Javascript is a single-threaded scripting language with non-blocking characteristics. The reason why javascript is non-blocking is that when processing asynchronous code, the main thread will suspend the task. When the asynchronous task is finished, the callback of the asynchronous task will be executed according to certain rules. Asynchronous tasks are divided into macrotast and microtast, which will be assigned to different queues. When all tasks in the execution stack are completed, it will first check whether there are events in the microtask queue, and execute the callback corresponding to the events in the microtask queue until it is empty. The callback of the event in the macro task queue is then performed. Repeating this process indefinitely to create an infinite loop is called an event loop.
Common microtasks include: Promise, MutationObserver, Object.observer, process.nextTick, etc
Common macro tasks include: setTimeout, setInterval, setImmediate, MessageChannel, requestAnimation, UI interaction events, etc

How do I register for microtasks?

NextTick will add the callback to the asynchronous task queue to delay execution. Before executing the callback, if you call nextTick repeatedly, Vue will not repeatedly add the callback to the task queue, but only add a task to the task queue. Multiple uses of nextTick will only add the callback to the callback list cache. Clears the list of callbacks and executes all callbacks in sequence as follows:
Const callbacks = [] let pending = false function flushCallbacks() Callbacks. Slice (0) callbacks. Length = 0 i < copies.length; I ++) {copies[I]()}} let microTimerFunc const p = promise.resolve () microTimerFunc = () => {// Register microtasks p.then(flushCallbacks) } export function nextTick(cb,ctx){ callbacks.push(()=>{ if(cb){ cb.call(ctx) } }) if(! Pending){pending = true // Set pending to true to ensure that the task does not repeatedly add microTimerFunc() in the sequential event loop}}Copy the code

Due to the high priority of microtasks, macro tasks may be required in some scenarios, so Vue provides a method withMacroTask to enforce the use of macro tasks. The concrete implementation is as follows:
Const callbacks = [] let pending = false function flushCallbacks() Callbacks. Slice (0) callbacks. Length = 0 i < copies.length; I ++) {copies[I]()}} let macroTimerFunc = function(){... } let useMacroTask = false const p = promise.resolve () microTimerFunc = () => {// register microtask export function withMacroTask(fn){ return fn._withTask || fn._withTask = function()=>{ useMacroTask = true const res = fn.apply(null,arguments) useMacroTask = false return res } } export function nextTick(cb,ctx){ callbacks.push(()=>{ if(cb){ cb.call(ctx) } }) if(! Pending){pending = true // Set pending to true, If (useMacroTask){macroTimerFunc()}else{microTimerFunc()}}}Copy the code

The withMacroTask method is used to enforce the use of a macro task. The useMacroTask variable is used to control the use of a registered macro task. The implementation of withMacroTask is very simple. Change back to false after the callback is executed.

How are macro tasks registered?

SetImmediate is preferred for the registration macro task, but compatibility issues mean it can only be used in IE, so MessageChannel is used as an alternative, and setTimeout is used if none of the above is supported. The concrete implementation is as follows:
if(typeof setImmediate ! == 'undefined' && isNative(setImmediate)){ macroTimerFunc = ()=>{ setImmediate(flushCallbacks) } } else if( typeof MessageChannel ! == 'undefined' && (isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]') ){ const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = ()=>{ port.postMessage(1) } } else { macroTimerFunc = ()=>{ setTimout(flushCallbacks,0) } }Copy the code

MicroTimerFunc is implemented through promise.then, but not all browsers support Promises, and when they don’t, they degrade to macro tasks
if(typeof Promise ! == 'undefined' && isNative(Promise)){ const p = Promise.resolve() microTimerFunc = ()=>{ p.then(flushCallbacks) } } else  { microTimerFunc = macroTimerFunc }Copy the code

If no callback is provided and the environment supports promises, nextTick returns a Promise as follows:
export function nextTick(cb, ctx) { let _resolve callbacks.push(()=>{ if(cb){ cb.call(ctx) }else{ _resolve(ctx) } }) if(! pending){ pending = true if(useMacroTask){ macroTimerFunc() }else{ microTimerFunc() } } if(typeof Promise ! == 'undefined' && isNative(Promise)){ return new Promise(resolve=>{ _resolve = resolve }) } }Copy the code

The above is the design of nextTick operation principle, and the complete code is as follows:
Const callbacks = [] let pending = false function flushCallbacks() Callbacks. Slice (0) callbacks. Length = 0 i < copies.length; I ++) {copies[I]()}} Let macroTimerFunc let macroTimerFunc let useMacroTask = false (Typeof setimerfunc! == 'undefined' && isNative(setImmediate)){ macroTimerFunc = ()=>{ setImmediate(flushCallbacks) } } else if( typeof MessageChannel ! == 'undefined' && (isNative(MessageChannel) || MessageChannel.toString() === '[Object MessageChannelConstructor]') ){ const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = ()=>{port.postMessage(1)}} else {macroTimerFunc = ()=>{setTimout(flushCallbacks,0)}} // Microtasks register if(typeof Promise ! == 'undefined' && isNative(Promise)){ const p = Promise.resolve() microTimerFunc = ()=>{ p.then(flushCallbacks) } } else {/ / down-cycled microTimerFunc = macroTimerFunc} export function withMacroTask (fn) {return fn. _withTask | | fn. _withTask = function()=>{ useMacroTask = true const res = fn.apply(null,arguments) useMacroTask = false return res } } export function nextTick(cb,ctx){ let _resolve callbacks.push(()=>{ if(cb){ cb.call(ctx) }else{ _resolve(ctx) } }) if(! Pending){pending = true // Set pending to true, If (useMacroTask){macroTimerFunc()}else{microTimerFunc()}} if(typeof Promise! == 'undefined' && isNative(Promise)){ return new Promise(resolve=>{ _resolve = resolve }) } }Copy the code

That’s all about how nextTick works.

The resources

Vue.js is easy to understand