preface
As we all know, updates in vue are asynchronous, such as this. MSG = XXX. It looks like it was updated immediately, but it wasn’t. It executes asynchronously, so let’s see how it works.
The above first
Let’s start with data changes
- Call this. MSG = XXX to change the data
- The dependent Watcher has been collected into the DEP during the data initialization phase, and the dep. Notify is executed to notify watceHR of the change
- The notify method iterates through all previous Watcher update calls and executes the current watcher instance in queueWatcher
The queueWatcher function code is in SRC \core\observer\scheduler.js
Adds the current watcher instance to a queue
export function queueWatcher (watcher: Watcher) {
// Get watcher's unique identifier
const id = watcher.id
// No matter how many data updates there are, the same watcher is pushed in only once
// I understand that this is why the value of a variable is changed several times in a single operation, but only one page update is made,
// The same variable depends on a certain watcher, so it will not be put into the Watcher queue, nor follow the logic
if (has[id] == null) {
// Caches the identity of the current watcher to determine whether it is duplicated
has[id] = true
// If the current state is not refresh, directly join the team
if(! flushing) { queue.push(watcher) }else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
FlushSchedulerQueue is executed. The watcher queue is already being updated.
QueueWatcher is retriggered by a data response update that is triggered when a watcher.run method is executed
// There is an operation to sort the watcher, so when the watcher is being updated it is already sorted, so it needs to be inserted in a specific position to keep the Watcher queue in order
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1.0, watcher)
}
// queue the flush
// waiting: the current flushSchedulerQueue is not yet executed because the state has not been reset. Waiting is still true
// Waiting indicates whether the flushSchedulerQueue is executed.
if(! waiting) { waiting =true
// Synchronize the refresh queue directly
if(process.env.NODE_ENV ! = ='production' && !config.async) {
// Synchronize execution
flushSchedulerQueue()
return
}
// Put the update queue function on the asynchronous queue
nextTick(flushSchedulerQueue)
}
}
}
Copy the code
FlushSchedulerQueue code is in the same directory
// Main function: We iterate through each of the Watcher’s run methods to update the data and view, and after we’ve done all of the methods, we reset the state to indicate that the flushing of the queue is being flushed, and to indicate whether watcher has exists. Indicates whether a NextTick waiting needs to be executed
function flushSchedulerQueue () {
// When the method is executed, set the state to being refreshed to continue executing the nextTick method
flushing = true
// Put watcher in the queue in order,
* 1. Ensure that the parent component's Watcher is updated before the child component's watcher, because the parent component is always created first and the child component is created later * 2. The component user's Watcher is executed before it renders the Watcher. * 3. If a component is destroyed during the execution of its parent, the child component is skipped. * /
queue.sort((a, b) = > a.id - b.id)
// Omit some code in between.// Run through all the watcher stored in queue
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
watcher.run()
}
// Since the queue is in a closure, it is cleared when the traversal is complete
queue.length = 0;
// has is to determine whether the current watcher is repeated, as a basis for whether to queue watcher
// All watchers in the queue have been executed, and the watcher that has been executed before can be added to the queue if it changes
has = {}
// waiting is a waiting for nextTick, the current refresh queue has finished, so it can be set to false, execute the next round of add asynchronous event queue method
// Flushing is a measure of whether the current asynchronous event is being executed
waiting = flushing = false
}
Copy the code
NextTick method source SRC \core\util\next-tick.js
export function nextTick(cb ? : Function, ctx ? : Object) {
let _resolve
// Add the update callback to the queue
// Wrap the incoming function with a try catch to avoid catching incoming callback errors when using $nextTick
// Add the callback function to the callback list whenever the nextTick function is executed
FlushSchedulerQueue executes all the watcher.run methods stored in the queue
callbacks.push(() = > {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')}}else if (_resolve) {
_resolve(ctx)
}
})
// Use pending to determine whether a task needs to be added to the task queue
If the last flushCallbacks function is still in the task queue, it will not be added to the task queue
// The first time it is executed, it is added to the task queue by default. Once added to the task queue, it indicates that there is no need to add flush function to the task queue
When the last flushCallbacks were executed, pending was changed to false, indicating that a flush function that flushes the list of callbacks can be added to the task queue
if(! pending) { pending =true
// This is the function that calls the empty callbacks array method and executes it,
timerFunc()
}
// $flow-disable-line
// Determine if the current environment supports Promises, and if so, return a promise object,
if(! cb &&typeof Promise! = ='undefined') {
return new Promise(resolve= > {
_resolve = resolve
})
}
}
Copy the code
The timerFunc() method, which mainly does some degrading operations, is the key to asynchrony
timerFunc = () = > {
Promise.resolve().then(flushCallbacks)
}
// If the current environment does not support it, some degradation will be performed until finally, using the macro task setTimeout to handle
Copy the code
Look at flushCallbacks. The task is to execute all callbacks
function flushCallbacks() {
The nextTick method will add a new task to the flushCallbacks task
pending = false
// Make a copy of all callbacks for execution
const copies = callbacks.slice(0)
// Then delete all callbacks
callbacks.length = 0
When the flushCallbacks in the microtask queue are added to the execution stack, all the functions in the callbacks are executed
FlushSchedulerQueue (flushSchedulerQueue) {flushSchedulerQueue (flushSchedulerQueue)
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
Copy the code
The role of basic key variables
- Waiting: Variable, which is the key to executing nextTick and adding the flushSchedulerQueue method, marks whether there is a flushSchedulerQueue method in the callbacks, such as a change to the same variable, FlushSchedulerQueue is asynchronous. Update flushSchedulerQueue flushSchedulerQueue flushSchedulerQueue flushSchedulerQueue flushSchedulerQueue There’s only one method in callbacks. Even though nexttick is executed when the first power is watcher and the flushSchedulerQueue is put into the callbacks, it looks like it’s already executed, but since queue is a closure variable, subsequent variables can still be added to queue,
- Flushing… FlushSchedulerQueue = flushSchedulerQueue = flushSchedulerQueue = flushSchedulerQueue = flushSchedulerQueue = flushSchedulerQueue = flushSchedulerQueue = flushSchedulerQueue = flushSchedulerQueue That’s why traversing watdher directly uses queue.length, the length will change.
- Pending: : Pending is the key to deciding whether or not to put the callbacks array update method on the asynchronous queue. This ensures that there is only one task to clear the callbacks in the asynchronous queue, which explains why manually executing multiple $nextTick methods in a row does not execute immediately. They still put their callbacks in callbacks, and when the task is complete, they execute all callbacks at once.
reference
- Vue source
- Juejin. Cn/post / 695156…