The definition of nextTick
Vue.nextTick([callback, context])
Copy the code
There are two ways to do things after a DOM update:
- Through MO object, listen for DOM changes, in its CB execution
- By controlling the task queue
VUE does not update the DOM every time it is modified for better performance. Instead, you update the DOM asynchronously.
Therefore, VUE’s rendering timing is between the completion of each task queue execution. It’s a macro task or a microtask that modifies data or modifies operations in the DOM every time in the task queue.
Task queue 1 completed -> Update DOM -> Task Queue 2 Completed -> update DOM….
Implement key points
- To get the updated DOM, you must place the CB in nextTick on the next task queue (macro task or micro task).
- Microtasks are always executed before macro tasks are executed. So, microtasks are better suited to the nextTick scenario. By placing the script from the nextTick callback into a promise.then(), you are guaranteed to execute after DOM updates.
- VUE demote policy (due to API compatibility issues), (Promise -> setTimeout)
Why use vue.nexttick ()
First, let’s understand how JS works.
- JS runtime mechanism (Event Loop)
- JS execution is single threaded, and it is based on event loops.
Event loop
- All synchronization tasks are executed on the main thread, forming an execution stack.
- Outside of the main thread, there is a task queue, and whenever an asynchronous task has a result, an event is placed in the task queue.
- The task queue is read when all synchronous tasks in the execution stack have been executed. Those corresponding asynchronous tasks will end the wait state and enter the execution stack.
- The main thread repeats step 3.
The main thread executes as a tick, and all asynchronous results are scheduled via the task queue. Event Loop is divided into macro task and micro task. No matter the macro task or micro task is executed, the next tick will be entered after completion and UI rendering will be performed between the two ticks.
Vue DOM update is performed asynchronously, that is, when data is modified, the view will not be updated immediately, but will listen for data changes and cache them in the same event loop. After all data changes in the same data loop are completed, the view will be updated uniformly. To ensure that you get an updated DOM, the vue.nexttick () method is set.
What it does:
The purpose of using vue.nexttick () is to get the updated DOM. Trigger timing: The callback to vue.nexttick () is executed as soon as the DOM is updated after the data changes in the same event loop.
The code in the same event loop completes execution -> DOM update -> nextTick Callback firesCopy the code
NextTick use
- By passing in the callback
- () by.
// Change the data
vm.message = 'changed'
// Want to use the updated DOM immediately. This does not work because the DOM has not been updated since message was set
console.log(vm.$el.textContent) // Will not get 'changed'
// This works. The nextTick code will be executed after the DOM update
Vue.nextTick(function(){
// The DOM is updated
// Can get 'changed'
console.log(vm.$el.textContent)
})
// Use as a Promise without returning calls
Vue.nextTick()
.then(function () {
// The DOM is updated
})
Copy the code
Application scenario:
You need to act on the new view after it has been updated.
-
DOM operations performed by the created() hook function in the Vue lifecycle must be placed in the callback function of vue.nextTick (). The reason: the DOM is not actually rendered when the created() hook function is executed.
-
When an operation is to be performed after the data changes, and the operation requires the use of DOM structures that change as the data changes, the operation should be placed in the callback function of ui.nexttick ().
Reason: Vue performs DOM updates asynchronously. As soon as a data change is observed, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is fired multiple times, it will only be pushed into the queue once.
Version analysis
- Version 2.6 gives preference to MicroTask as an asynchronous delay wrapper and is relatively simple to write.
- In version 2.5, nextTick is implemented as a combination of microTimerFunc and macroTimerFunc, and the deferred call priority is: Promise > setImmediate > MessageChannel > setTimeout.
Other Application Scenarios
- Click the button to display the input box that was hidden with V-show = false and get focus.
showsou(){
this.showit = true / / modify v - show
document.getElementById("keywords").focus() // In the first tick, there is no input field, and therefore no focus
}
Copy the code
Is amended as:
showsou(){
this.showit = true
this.$nextTick(function () {
// The DOM is updated
document.getElementById("keywords").focus()
})
}
Copy the code
- Click to get the element width.
<div id="app">
<p ref="myWidth" v-if="showMe">{{ message }}</p>
<button @click="getMyWidth">Gets the width of the p element</button>
</div>
Copy the code
getMyWidth() {
this.showMe = true;
//this.message = this.$refs.myWidth.offsetWidth;
TypeError: this.$refs.myWidth is undefined
this.$nextTick((a)= >{
// After the DOM element is updated, we can get the attributes of the P element
this.message = this.$refs.myWidth.offsetWidth; })}Copy the code
showsou(){
this.showit = true
this.$nextTick(function () {
// The DOM is updated
document.getElementById("keywords").focus()
})
}
Copy the code
Source analyses
The nextTick implementation has a separate JS file to maintain it. The nextTick source code in SRC /core/util/next-tick.js is divided into two main parts: capability detection and callback queue execution in different ways based on capability detection
Ability to detect
Since macro tasks take more time than microtasks, microtasks are preferred when the browser supports them. If the browser does not support microtasks, then use macro tasks.
// An empty function can be used as a function placeholder
import { noop } from 'shared/util'
// Error handler
import { handleError } from './error'
// Whether it is an IE, IOS, built-in function
import { isIE, isIOS, isNative } from './env'
// Use the MicroTask identifier, here because Firefox cannot trigger a MicroTask when <=53, and reference it in modules/events.js for security exclusion
export let isUsingMicroTask = false
// To store all callbacks that need to be executed
const callbacks = []
// Indicates whether the callback function is executing
let pending = false
// Iterate over the callbacks and execute the corresponding callback function
function flushCallbacks () {
pending = false
// The reason for the copy is:
// Some callbacks will be added to the callbacks during execution
// For example, the $nextTick callback function has $nextTick
// The latter should be executed in the next round of nextTick
// So make a copy of the current one and iterate through the current one to avoid endless execution
const copies = callbcks.slice(0)
callbacks.length = 0
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc // flushCallbacks is used to delay calls to the flushCallbacks function
// In 2.5, we use (macro) tasks (in combination with microtasks).
// However, a subtle problem arises when the state changes before redrawing
// (for example #6813,out-in conversion).
// Also, the use of (macro) tasks in event handlers can cause some strange behavior
// So, we now use microtasks everywhere again.
// Use Promise first
if(typeof Promise! = ='undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = (a)= > {
p.then(flushCallbacks)
// IOS UIWebView, promise.then callbacks are pushed to the microTask queue, but the queue may not execute as expected
// Therefore, add an empty timer to force microTask to execute
if(isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (isNative(MutationObserver) || MutationObserver.toString === '[object MutationObserverConstructor]')) {
// Use a native MutationObserver when a native Promise is not available
PhantomJS, iOS7, Android 4.4
let counter = 1
// Create an MO instance that listens for changes in the DOM and executes a callback to flushCallbacks
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true // Set true to observe changes in the target
})
// Each timerFunc execution switches the contents of the text node between 0/1
// After the switch, copy the new value to the text node observed by MO
// A change in the content of the node triggers a callback
timerFunc = (a)= > {
counter = (counter + 1) % 2
textNode.data = String(counter) // Trigger the callback
}
isUsingMicroTask = true
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
timerFunc = (a)= > {
setImmediate(flushCallbacks)
}
} else {
timerFunc = (a)= > {
setTimeout(flushCallbacks, 0)}}Copy the code
Deferred call priority is as follows:
Promise > MutationObserver > setImmediate > setTimeout
reference
- Segmentfault.com/a/119000002…
- Blog.csdn.net/a380776767/…
Vue data modification code analysis:
- Modifying the data directly in Vue data to update what’s happening on the DOM
- What did nextTick do?
Assume that the current template code is
<div id="a">{{a}}</div>
Copy the code
Here we write the following code in the Mounted hook
this.a = 'a';
this.$nextTick(function(){
console.log($('#a') [0].textContent);
})
Copy the code
Data can be modified multiple times in a single task. Instead of telling the WATcher to update the DOM every time a change is made, we can add the watcher to the update array. When the task code is executed (that is, all synchronized code is executed), the round of data modification is finished. At this point we can trigger the watcher’s update operation, so that no matter how many times the task has changed, we will only update the DOM once.
The point of nextTick is to execute the flushBatcherQueue step over watcher in a microtask, whether it’s MO or promise.then. In situations where neither of these is compatible, you are forced to use setTimeout instead. But setTimeout puts the callback function on the MacroTask queue, and the browser triggers UI rendering when it clears the microTask queue, so it wastes the browser UI rendering opportunity before it. (It takes at least two UI rendering sessions to render the updated DOM)
Vue’s DOM update timing setting
At the end of each event loop, there is a UI Render step that updates the DOM
for(let i=0; i<100; i++){
dom.style.left = i + 'px';
}
Copy the code
Will the browser make 100 DOM updates? Obviously not. It’s too performance intensive. In fact, all 100 for loops belong to the same task, and the browser only updates the DOM once the task is finished.
So here’s the idea: wouldn’t it be possible to access the updated DOM if the code in nextTick was executed after the UI Render step?
This is the idea behind VUE, not using MO to listen for DOM changes, but using queue control. So how does Vue do queue control? It’s natural to think of setTimeout, putting the code nextTick is going to execute at the end of the queue as the next task.
However, it’s not that simple. Vue’s queue control is well thought out (and has been changed many times). Macrotasks are not executed until all microtasks have been executed. Microtasks have a higher priority.
This feature of MicroTask makes it the perfect choice for queue control. Vue also calls nextTick to do asynchronous queue control inside DOM update. When we call nextTick ourselves, it appends our own callback function to the microtask that updates the DOM, ensuring that our code executes after the DOM update and avoiding the multiple execution problems that setTimeout might have.
Common microTasks are: Promise, MutationObserver, Object.Observe (discard), and Process.nextTick in NodeJS.
Yi? I see MutationObserver. Is it possible that Vue is using MO to take advantage of its microtask features instead of DOM listening? Right, that’s it. The core is microTask, with or without MO. In fact, Vue has removed the MO code in version 2.5 because it is a new HTML5 feature and is buggy on iOS.
The best microTask strategy is Promise, which is embarrassingly new to ES6 and has compatibility issues, so VUE is faced with a downgrade strategy. #### Vue’s degradation strategy As mentioned above, the best option for queue control is MicroTask, and the best option for microtask is Promise. But if the current environment does not support promises, vUE will have to be demoted to macroTask for queue control.
What are the options for MacroTask? As mentioned earlier, setTimeout is one, but it is not ideal. Since setTimeout executes at a minimum interval of about 4ms, it’s a little delayed. Are there any other plans? In vue2.5 source code, macrotask degradation scheme in turn is: setImmediate, MessageChannel, setTimeout.
SetImmediate is the ideal solution, but only IE and NodeJS support it.
MessageChannel’s onMessage callback is also a MicroTask, but it’s also a new API, and faces compatibility challenges…
So the last option is to use setTimeout, although it has execution delays and may cause multiple renderings.
A small summary
Vue’s nextTick method is designed to work with the following elements:
- Vue uses an asynchronous queue to control the DOM update and nextTick callbacks
- Microtask, because of its high priority nature, ensures that microtasks in the queue are completed before an event loop
- Vue had to downgrade microTask to MacroTask due to compatibility issues
Conclusion:
- NextTick, adds a microtask or macro task to the task queue. (Prioritize microtask promises)
- This is the idea behind nextTick Vue, not using MO to listen for DOM changes, but using queue control. So how does Vue do queue control? It’s natural to think of setTimeout, putting the code nextTick is going to execute at the end of the queue as the next task. There’s a lot more to follow
- The best choice for queue control is microTask, and the best choice for microTask is Promise
- Vue performs DOM updates asynchronously
- As soon as Vue observes a change, Vue will start a queue to store the data changes that occur in the same event loop.
- NextTick actually uses the task queue mechanism to do DOM update.
- In IOS, promise.then callback does not execute as expected, add an empty timer to force microTask to execute
- Vue’s rendering is a UI rendering between two ticks
- Change data -> Start queue (store all changes)-> notify watcher-> update the DOM between the two ticks according to the event loop mechanism
- Event loop (main thread repeats step 3)
- All synchronization tasks are executed on the main thread (forming a stack) ->
- Execute asynchronous task after synchronous task is finished ->
- Execute the microtask first and then the macro task
In Vue, this.a=”a”, data is modified, simple version process:
- Defineproperty hook set is used to notify all watcher who subscribed to a data. Watcher receives the notification and puts the data modification into the update array (task queue). After the synchronous task of main thread completes, the asynchronous task is executed to update the DOM. Dom updates in VUE are asynchronous.
This triggers the MO to add its callback function to the microTask queue
- The MO object detects the DOM update, and instead of calling the callback directly, it puts the callback into the microtask and waits until the microtask is finished to do what happened after the DOM update (the script in nextTick starts executing).
Refine the process (VUE does not use MO objects, so this process needs to be adjusted in 6,7,8,9,10 steps)
- A modified
- The data is listening to a being modified
- Notifies watcher who has subscribed to a data.
- The Watcher receives a notification and places the watcher in the update array. Waiting for the update
- Updates the DOM between the two ticks of the event loop
- The MO object detects that the DOM has changed
- Put his callback in the microtask queue and wait for execution.
- Is nextTick used?
- If yes: nextTick will turn the microtask into a synchronous task.
- If not: follow the normal event loop to update the DOM
6,7,8,9,10 of the above process need to be modified as follows:
- After DOM rendering is complete, the next task queue is executed
- Adjust the task queue and put the CB in nextTick into the task queue
- Task queue – Demote policy (micro task first, macro task later)