Interviewer: What about $nextTick in Vue?
Company: Good future
What is NextTick
The official definition of it
A deferred callback is performed after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM
That is, you can immediately get the dom updated data for updating
For example, the
Html structure
<div id="app"> {{ message }} </div>
Copy the code
Build a VUE instance
const vm = new Vue({
el: '#app'.data: {
message: Original value}})Copy the code
Modify the message
this.message = 'Modified value 1'
this.message = 'Modified value 2'
this.message = 'Modified value 3'
Copy the code
When you try to get the latest DOM node of the page, you get the old value instead
console.log(vm.$el.textContent) / / the original value
Copy the code
This is because vUE does not immediately update the Dom when a change is detected in the Message data. Instead, it places the change in an asynchronous operation queue
If we keep modifying the same data, the asynchronous operation queue will also be de-duplicated
After all data changes in the same event loop are complete, events in the queue are processed for DOM updates
Why nexttick
For example
{{num}}
for(let i=0; i<100000; i++){
num = i
}
Copy the code
Without the nextTick update mechanism, the view would be updated every time num was updated (the above code would update the view 100,000 times). With the nextTick mechanism, you only need to update the view once, so nextTick is essentially an optimization strategy
Two, use scenarios
You can use vue.nexttick () if you want to get the updated DOM structure immediately after modifying the data.
The first argument is: callback function (to get the most recent DOM structure)
The second argument is: execute function context
$el.textContent) // The original value vue.nexttick (function () {// The DOM is updated Console. log(vm.$el.textContent) // Modified value})Copy the code
To use the vm.nexttick () instance method within a component, you only need to use the this.nexttick () instance method
Just go through this.nexttick () instance method just go through this.nexttick (), and this in the callback will automatically bind to the current Vue instance
this.message = 'Modified value'
console.log(this.$el.textContent) // => 'original value'
this.$nextTick(function () {
console.log(this.$el.textContent) // => 'Modified value'
})
Copy the code
$nextTick() returns a Promise object that can do the same thing with async/await
this.message = 'Modified value'
console.log(this.$el.textContent) // => 'original value'
await this.$nextTick()
console.log(this.$el.textContent) // => 'Modified value'
Copy the code
Three, the implementation principle
/ SRC /core/util/next-tick.js
Callbacks are asynchronous operation queues
Pending indicates that the timerFunc function can only be executed once at a time
export function nextTick(cb? :Function, ctx? :Object) {
let _resolve;
// Cb callbacks are pushed into the callbacks array with uniform processing
callbacks.push(() = > {
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();
}
// 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
The timerFunc function defines which method to call based on which method is supported by the current environment.
Promise. Then, MutationObserver, setImmediate, setTimeout
Use either of the above methods to perform the degrade operation
export let isUsingMicroTask = false
if (typeof Promise! = ='undefined' && isNative(Promise)) {
// Judgment 1: Whether to support Promises natively
const p = Promise.resolve()
timerFunc = () = > {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Judgment 2: Whether MutationObserver is supported natively
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () = > {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
// Judgment 3: Whether setImmediate is native supported
timerFunc = () = > {
setImmediate(flushCallbacks)
}
} else {
// Judgment 4: None of the above, use setTimeout directly
timerFunc = () = > {
setTimeout(flushCallbacks, 0)}}Copy the code
Both micro and macro tasks are used in flushCallbacks
Here we make a copy of the function inside the Callbacks and leave the callbacks empty
Execute the functions inside the Callbacks in turn
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
Summary:
Putting the executing function into the microtask or macro task loops events to the microtask or macro task, and the executing function executes the callbacks in turn