What is nextTick
NextTick is one of the core methods of VUE, and it’s used a lot. But you just know that a callback is executed after the Dom update is complete. I’m curious about how it works. Look at the source code analysis. I think I know a little bit why. There must be a lot of mistakes. But first share understanding wave.
Even loops
1. Single thread of JS
We know that one of the hallmarks of JavaScript is a single thread that has a unique event loop
As a browser scripting language, JavaScript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems
2. What is an event loop?
1. All synchronization tasks are executed on the main thread, forming an execution context stack. In addition to the main thread, if there are asynchronous tasks, there is a "task queue" in which an event 3 is placed whenever the asynchronous task has a result. Once all synchronization tasks in the Execution stack are completed, the system reads the Task queue to see what events are in it. Those corresponding asynchronous tasks then end the wait, enter the execution stack, and start execution 4. The main thread repeats step 3Copy the code
Assume JavaScript has two threads at the same time (events)
-
A thread adds something to a DOM node,
-
Which thread should the browser use when another thread removes the node?
So the environment in which JS is run determines that js is essentially a single thread
Single threading means that all tasks need to be queued until the first one is finished before the next one can be executed
So if the code in front doesn’t work, it doesn’t work in the back. That’s gonna be a long time. So no,
So JS has a synchronous and asynchronous concept
3. Synchronous task (code), asynchronous task (code)
We talk a lot about synchronous code, asynchronous code. Sync code is executed first. Asynchronous code is delayed
Everything is done with the help of the function call stack. It’s all about calling functions.
In simple terms
Synchronization code
A function is synchronous if the caller gets the expected result (that is, gets the expected return value or sees the expected effect) when it returns
Asynchronous code
A function is asynchronous if the caller does not get the expected result when it returns, but needs to get it by some means or wait some time in the future
But code that is executed asynchronously is not executed immediately. How to prioritize?
In general, there are three types of asynchronous tasks
2. Load resource data, such as load, when the response to all data, the task is completed, and queue to the task queue 3. Timer, includingsetThe Interval,setTimeout, when the time is up. Queue to the task queueCopy the code
This way, when the synchronization code for the main thread is complete, JS starts looking for asynchrony from the task queue. Whoever is first, go out first. Go to the stack and execute. Until an event is completed. Continue back to the main thread, execute. Find asynchronous,… After completing an event… Event loop…
The main thread is stack first in first out, and the task queue is queue first in first out
But as ES6 evolved and made promises, asynchronous code became interesting. When the main thread is finished, it looks for asynchronous tasks in the task queue. But there are asynchronous tasks. After execution, it does not want to be immediately executed and cleared by the main thread. Instead, I want to do other little things right away. For example.then().then()… This is where asynchronous tasks get complicated. The main thread doesn’t know whether to do your asynchronous small thing first or your asynchronous big thing first…
So this is divided into the concept of macro tasks and micro tasks
4. Macro tasks (code), micro tasks (code)
Synchronized code executes directly. Asynchronous code is divided into macro tasks and micro tasks
Asynchronous tasks of different types enter different task queues
Macro tasks are added to the macro task queue,
Microtasks are added to the microtask queue.
After the synchronization task in the execution stack is completed, the main thread will first check the microtask in the task queue. If there is no microtask, it will remove the first event from the macro task queue and add it to the execution stack. If so, all the events in the microtask queue are added to the execution stack in turn. After all the microtask events are executed, the first event is removed from the macro task and added to the execution stack, and the cycle repeats.
Macro task: script,setA Timeout,setInterval microtasks: Promise, process.nextTick MutationObserverCopy the code
Look at the following set of code.
console.log('start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('end'); Script start script end' promise1 promise2 setTimeoutCopy the code
Go through the event loop
Each macro task is an event
The first loop is ——–.
There are a lot of code blocks in the execution stack, executing from scratch
- Synchronize code output to start output
- The macro task setTimeout is suspended in the macro task queue
- The microtask code Promise is followed by two then promisE1 promisE2 in the microtask queue
- Sync code end output
- By definition, the main thread continues to find the microtask after executing in the same step. The execution. Output promise1 promise2
- The microtask queue is empty. Start the second macro task
The second loop is ——–.
- The execution stack should execute. empty
- The microtask is empty. It’s done
- Go to the macro mission, Emma. Found one. setTimeout
- Output setTimeout
Let’s do another example
console.log('1')
// This is a macro task
setTimeout(function () {
console.log('2')});new Promise(function (resolve) {
// Here is the synchronization task
console.log('3');
resolve();
// Then is a microtask
}).then(function () {
console.log('4')
setTimeout(function () {
console.log('5')}); }); The results of1 3 4 2 5
Copy the code
The first event loop
- The method in the execution stack starts executing
- Synchronize task output 1
- When a setTimeout is encountered and does not execute, place the macro task queue hanging flag setTimeout1
- I’m going to see that the promise constructor is synchronous output 3 and then I’m going to see the microtask. Mount the microtask queue (which has macro task nesting. Don’t bother, just put the microtasks)
- Synchronization is complete. Look for microtasks. The output of 4. There’s a macro mission in Emma. Put in the macro task. Mark setTimeout2
- Synchronization is complete. Microtasks are over. Next macro task
Second event loop
- The method in the execution stack starts executing
- No synchronization code
- Nor do microtask queues
- Find macro task setTimeout1. The output of 2
- Next macro task
The third event loop
- The method in the execution stack starts executing
- No synchronization code
- Nor do microtask queues
- Find macro task setTimeout2. The output of 5
- Next macro task.. There is also no event loop that executes three times
We just need to remember that when the current stack finishes, all events in the microtask queue are processed immediately, and then an event is fetched from the macro task queue. Microtasks are always executed before macro tasks in the same event loop. Each macro task starts an event loop
Third, nextTick source code analysis
Understand the event loop mechanism and macro tasks, micro tasks. A little bit about source parsing,
Vue.js updates the DOM asynchronously by default. Whenever a data change is observed, Vue starts a queue that caches all data changes within the same event loop.
If a watcher is triggered more than once, it will only be pushed into the queue once. After the next event loop, Vue empties the queue and makes only necessary DOM updates.
So if you change data, the DOM doesn’t update immediately,
Instead, it updates the next time the event loop empties the queue. To wait for vue.js to finish updating the DOM after the data changes,
Vue.nexttick (callback) can be used immediately after data changes. The callback is called after the DOM update is complete.
Event loop ==> Updates dom==> Event loop ==> updates DOM
Source location
1. Source code analysis
Vue’s source code for nextTick is maintained separately under the SRC /core/util/next-tick.js directory. The vue code varies slightly from version to version. But most of the differences are in the order in which VUE uses macro or micro tasks
In fact, the nextTick source code part of the code is not much, about 100 lines of code, but short and sharp. In fact, there are four major parts
1. Define variables, set an array of stored functions, loop through the array of functions, and execute. 2. Determine which conditional environments use which asynchronous delay functions (macro tasks, micro tasks) (this is the core key code) 3. Wrap and export the nextTick function, setting this to point to (so the component can use the function properly) 4. Return a promise-like call when nextTick passes no function arguments (sort of an optimization)Copy the code
2. Code analysis
1. Define variables and set an array to store functions
Introduces introduced modules and defined variables.// noop is an empty function that can be used as a function placeholder
import { noop } from 'shared/util';
// Vue internal error handlers
import { handleError } from './error';
IE/IOS/ built-in function
import { isIE, isIOS, isNative } from './env';
// Use the MicroTask identifier
export let isUsingMicroTask = false;
// Store the functions executed in an array
const callbacks = [];
NextTick Execution status
let pending = false;
// Executes each function through the array of functions
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for (let i = 0; i < copies.length; i++) { copies[i](); }}Copy the code
2. Use of asynchronous delay functions in different environments and conditions
Next comes the core asynchronous delay function. The different Vue versions here are using different strategies.The core asynchronous delay function is used to asynchronously delay calls to the flushCallbacks function
let timerFunc;
// timerFunc uses native Promises in preference
// If the browser environment supports promise microtasks, use them preferentially to loop through the next event faster
if (typeof Promise! = ='undefined' && isNative(Promise)) {
const p = Promise.resolve();
timerFunc = (a)= > {
p.then(flushCallbacks);
// the IOS UIWebView, promise. then callback is pushed to the microTask queue but the queue may not execute as expected.
// Therefore, add an empty timer to "force" the execution of the microTask queue.
if (isIOS) setTimeout(noop);
};
isUsingMicroTask = true;
TimerFunc uses the native MutationObserver when the native Promise is not available
} else if (
!isIE &&
typeofMutationObserver ! = ='undefined' &&
(isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]')) {let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true}); timerFunc =(a)= > {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
// If native setImmediate is available, timerFunc uses native setImmediate
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
timerFunc = (a)= > {
setImmediate(flushCallbacks);
};
} else {
// There is no way to use the least efficient macro task setTimeout
timerFunc = (a)= > {
setTimeout(flushCallbacks, 0); }; } this part is actually very simple. As we all know, Event Loop can be divided into Macro task and micro task. After executing the micro task, it will enter the next Event Loop and perform UI rendering between the two Event loops. However, macro tasks take more time than microtasks, so microtasks are preferred when supported by browsers. If the browser does not support microtasks, use macro tasks. However, the efficiency of various macro tasks can vary, depending on the browser's support. Summary priorities in one sentence: MicroTask first.Promise> MutationObserver > setImmediate > setTimeoutCopy the code
3. Encapsulate and export the nextTick function
NextTick function. It takes two arguments: cb callback function: is the function to delay execution; CTX: specifies the CB callback functionthisPoint; The Vue instance method $nextTick further wraps, setting CTX to the current Vue instance.export function nextTick(cb? : Function, ctx? : Object) {
let _resolve;
// Cb callbacks are pushed into the callbacks array with uniform processing
callbacks.push((a)= > {
if (cb) {
// add try-catch to cb callback
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick'); }}else if(_resolve) { _resolve(ctx); }});// Execute the asynchronous delay function timerFunc
if(! pending) { pending =true;
timerFunc();
}
Copy the code
4. Return a Promise when nextTick passes no function arguments
// Return a promise-like call when nextTick passes no function arguments
if(! cb &&typeof Promise! = ='undefined') {
return new Promise(resolve= >{ _resolve = resolve; }); }}Copy the code
What does vue.nexttick (cb) do each time it is called:
The CB function is processed into the callbacks array,
Execute the timerFunc function, defer calling the flushCallbacks function,
Iterate over all the functions in the Callbacks array.
Deferred call priorities are as follows:
Promise > MutationObserver > setImmediate > setTimeout
5. Write a Simple nextTick
Of these four parts, the third is the core and the most complex. In fact, this is vUE in order to adapt to a variety of different application environment to make a lot of adaptation and compatibility consideration. Let’s say we don’t think about these cases. We use the least efficient setTimeout for asynchronous delay (vue also uses setTimeout for the last else). Just for the last impossible compromise)
These are the easy nextTick methods…
// Define an empty array
let callbacks = []
let pending = false
// loop through the methods inside the function
function flushCallback () {
pending = false
let copies = callbacks.slice()
callbacks.length = 0
copies.forEach(copy= > {
copy()
})
}
// Set up a function to export nextTick, insert the method into the defined array, and execute the flushCallback method from the previous step.
function nextTick (cb) {
callbacks.push(cb)
if(! pending) { pending =true
setTimeout(flushCallback, 0)}}// Return a promise-like call when nextTick passes no function arguments
if(! cb &&typeof Promise! = ='undefined') {
return new Promise(resolve= > {
_resolve = resolve;
});
}
Copy the code
There must be a lot of mistakes, I hope you correct them. Thank you very much
Refer to the link
Vue. Js source code
Vue. Js technology decryption
Analysis of Vue nextTick