Preface
The Vue official website introduces asynchronous updates as follows:
- Vue is executed asynchronously when updating the DOM.
- As long as data changes are heard, Vue will turn on a queue and buffer 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.
- This removal of duplicate data during buffering is very important to avoid unnecessary calculations and DOM operations
After Vue is hijacked on data, when the object is set, the view update will be triggered.
Update logic
The following example analyzes the view update processing logic:
<div>{{ message }}</div> <button @click="handleClick">renew</button> new Vue({ data: { message: '' }, methods: { handleClick() { = (); } } })
When clicking the update button, an assignment operation will be performed on the hijacked attribute message, and the set operation will be triggered.
set operation
The set function setting is actually the most core logic to trigger view updates. The specific code logic is as follows:
set: function reactiveSetter (newVal) { // Other logic // Trigger view update (); }
Each attribute will correspond to a Dep object. When assigning the attribute, the notify instance method of Dep will be called. The function of this instance method is to notify the view to update.
Dep notify instance method
The code logic of the notify instance method is as follows:
= function notify () { // stabilize the subscriber list first var subs = (); for (var i = 0, l = ; i < l; i++) { subs[i].update(); } };
The storage in subs is a watcher object. Each Vue instance has a watcher object associated with view update. The creation of this object is in the $mount stage. For details, please see the previous article.The overall process of creating a Vue instance。
The association between the Dep object representing the attribute and the watcher object is established when the specific attribute is obtained during the render function call stage, i.e., dependency collection.
The notify method will execute the update method of all watcher objects associated with the current attribute, and there will inevitably be a watcher related to the view update.
The watcher object is actually divided into two categories according to classification:
- For view update related, each Vue instance has a watcher object of this class.
- Logical calculation related, compute properties and watch listening create watcher objects
Watcher update instance method
The code logic of the update instance method is as follows:
= function update () { /* istanbul ignore else */ if () { = true; } else if () { (); } else { queueWatcher(this); } };
lazy and sync are both properties of Watcher, which respectively represent:
- lazy: means lazy processing, that is, delay correlation processing, used to process computational properties
- computeredsync: means synchronous execution, that is, the view will be updated immediately when the property update is triggered.
From the above logic, it can be seen that the default queueWatcher processing means opening a queue and buffering all data changes that occur in the same event loop, that is, the view is updated asynchronously.
One thing to note here is:
There must be a watcher object with view update in queueWatcher, and there will be no watcher corresponding to the computed attribute computed (the lazy attribute corresponding to computed is true by default). There may be a watcher object with user nature corresponding to the watch API.
queueWatcher execution logic
function queueWatcher (watcher) { var id = ; if (has[id] == null) { has[id] = true; if (!flushing) { (watcher); } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. var i = - 1; while (i > index && queue[i].id > ) { i--; } (i + 1, 0, watcher); } // queue the flush if (!waiting) { waiting = true; nextTick(flushSchedulerQueue); } } }
In fact, the surface logic is mainly divided into 3 points:
- For the same watcher object, use the has object structure +id as key to determine whether the corresponding watcher object already exists in the queue. If it exists, it will not be added to the queue
- How to distinguish between the flushing tag when clearing the queue and under normal circumstances, how to add a watcher to the queue
- Through the waiting flag, distinguish whether to perform nextTick, i.e. clear queue action.
Because queue is a global variable, add the watcher object to the queue before this step. If waiting is true, it indicates that nextTick has been called to implement asynchronous processing queue. Do not call nextTick again
From the above overall logic, we can see that the logic of queueWacther mainly has two points:
- Determine whether to repeat the watcher, add it to the queue for non-repeated watchers
- Call nextTick to enable asynchronous processing queue operation, that is, flushSchedulerQueue function execution
nextTick + flushSchedulerQueue
The nextTick function actually has the same logic as $nextTick. The main difference is the difference in context, that is, the difference in this binding value of the function.
Use macroTask API or microTask API to execute flushSchedulerQueue
The flushSchedulerQueue function is the specific processing logic of queue, the main logic is as follows:
function flushSchedulerQueue () { flushing = true; var watcher, id; // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. (function (a, b) { return - ; }); for (index = 0; index < ; index++) { watcher = queue[index]; id = ; has[id] = null; (); } var activatedQueue = (); var updatedQueue = (); resetSchedulerState(); // call component updated and activated hooks callActivatedHooks(activatedQueue); callUpdatedHooks(updatedQueue); }
The main logic of the flushSchedulerQueue function can be summarized into the following points:
- Sort the watcher objects in the queue queue
- Iterate through queue to execute the run method of each watcher object
- Reset the relevant status of the control queue for the next round of updates
- Execute the updated and activated life cycles of components
I won't expand it here. It should be noted that activated is for special processing of components under keep-alive. The updated life cycle is first of the child component and then the parent component. The watcher object of the queue queue is arranged in the order of the parent component and the child component. Therefore, the trigger of the updated life cycle in the source code is triggered by traversing the queue in reverse order.
First, let’s talk about the run instance method of the watcher object. The main logic of this method is to execute the functions corresponding to the getter attribute and cb attribute of the watcher object.
As mentioned above, the watcher object is actually divided into two categories according to classification:
- For view update related, each Vue instance has a watcher object of this class.
- Logical calculation related, compute properties and watch listening create watcher objects
The getter attribute and cb attribute of the watcher object correspond to the actual processing logic of various watchers above. For example, the getter attribute corresponding to the watch API is the listening item, and the cb attribute is the specific processing logic.
Why do we need to sort the watcher objects in queue?
In fact, there are relevant instructions in the Vue source code, which mainly involves the creation of nested component Vue instances, render watch and user watch creation.
Each component is a Vue instance. Nested component creation is always created from the parent component Vue instance, and Vue instances of child components are created only during the parent component patch stage.
And this order determines the id value of the watcher object:
All watcher object ids of parent component < All watcher object ids of child component
The render watch is actually a watcher object related to view update. This object is created only at the end of the creation of the corresponding Vue instance, that is, the mount stage. It is a watcher object created later than the user watch, that is, the computed attributes and the watch API. Therefore:
render watch id < all user watch id
The child component may be the update trigger source. If the parent component also needs to update the view, the watcher object position of the child component in the queue queue will be before the parent component's watcher object, which ensures that the watcher object in the queue is:
When view updates, the parent component always starts the update operation before the child component, and the watcher rendered by the corresponding view of each component is finally executed (that is, the logic corresponding to the user watcher object is executed first)
Summarize
The process of Vue asynchronous update is still very clear:
- Assigning attributes to trigger the execution of the notify method of the Dep object
- Then execute the update method of the Watcher object to save the object to the queue queue
- Then call the mircoTask API or macroTask API to execute tasks in queue
- Sorting the watchers in the queue to ensure the correctness of sequential execution, and calling its corresponding run method to implement view updates and related logical update operations.
The above is personal experience. I hope you can give you a reference and I hope you can support me more.