In this example, mutation was triggered for three consecutive times. How many times would CB be executed in watch?
The answer is once.
So why once? This article will briefly discuss the principle of batch updates in Vue around this explanation.
MSG = XXX (MSG = XXX, MSG = XXX, MSG = XXX, MSG = XXX) Trigger the update method of all Watcher subscribed to this key.
So this is the concept of a Watcher, so what is this Watcher?
Wathcer is an observer of the key, and Watcher calls its update method when the key is set.
In this particular example, MSG is set to a different value, so the Watcher generated by the option watch will execute the update method on its prototype. The final purpose of this method is to execute cb on the instance. This is the method of the MSG corresponding console.log(‘ listen to mutation’).
The set operation on MSG key can fetch all the Watcher of the key. How the Watcher is generated and why the set operation can fetch all the Watcher of the key is beyond the scope of this article.
The key to the problem is Watcher’s update method, which was previously described as executing cb, in this case console.log(‘ listen to mutation’), but first consider a question:
Can we listen for a mutation caused by a set and execute cb immediately?
Obviously not.
Here’s an example:
for (leti = 0; i < 1000; I++) {this. MSG = 'loop${i}`; }Copy the code
If Watcher responded to this operation with an update for every 1000 times, there could be a significant performance overhead. In this example, update is just console.log, but if CB is an expensive method, it can cause performance problems.
So the UPDATE operation must be asynchronous.
I know I want to do it asynchronously, but how? Vue puts cb calls into a Micro task or Macro task queue, The Promise, MutationObserver, and setImmediate APIS that mediate the microtask queue depend on whether the operating environment supports the Promise, MutationObserver, and setImmediate apis. If not, use the setTimeout API to put cb calls into the macro task queue.
The cb call is executed after all synchronization code has been executed, whether it is placed on a microtask queue or a macro task queue. This relates to the knowledge of event loops, because all synchronized code is always executed first and then sequentially executed from the microtask queue, which is empty before an execution is taken from the macro task queue. If there are still tasks in the microtask queue, then the loop will continue, which is called the Event loop.
Vue only promised to perform the update request from Watcher at some point in the future, that is, the cb that called it. Update requests from the Watcher will not be processed until they are called.
Below to see the source code (p.s. For the first time to see the source code, the vue – cli generated by the project, effective source in node_modules/vue/dist/vue. Runtime. Esm. Js) :
function queueWatcher (watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
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.
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// queue the flush
if(! waiting) { waiting =true; nextTick(flushSchedulerQueue); }}}Copy the code
First, queueWatcher is what Watcher does when executing the update method. We notice that there are three flags, has[ID], flushing and waiting. We mainly explore the meaning of these three flags to understand the source code.
We MSG for the three set operation, corresponding to three Watcher of the update method, corresponding queueWatcher three times, and the parameters of the line every time pass these three times, are all the same Watcher, so for the first time from the [id] is null, the first set to true, The last two were straight over. The first mutation on the Watcher will tell you that the Vue has been added to the queue, which will be processed later. Other mutations on the same Watcher will not be received.
So the first mutation actually added the Wathcer instance to the queue, and at some point in the future, traversed the queue and called the CB of all Watcher instances.
And the flushing variable, if we actually look at the if else, the if branch the watcher is going to be put at the end of the queue, and the else branch is going to be put at the end of the queue by the WATcher’s ID, so all the watchers in the queue are going to be sorted by their ID, If the watcher of this id has already been executed, it will be put to the next item in the queue, and the cb of the watcher will be executed next.
And you can see that flushing is to make sure that all the Watchers in the queue are always in ascending order by ID, so why do we do that? Vue source code to give three comments:
// 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.
Copy the code
To ensure that the parent component executes the update in sequence, the user-created wathcer is executed before the Render Watcher, and on the parent component’s Watcher If a child component is destroyed during run, its watcher will not be executed.
I think it’s good enough to know that all the watchers in this queue are in ascending order by ID, and that id was incremented when the watcher was created, and you’ll understand why that’s the case later.
Finally, waiting, which is simple enough to ensure that no new traversal of the queue is started until a new traversal has been completed.
OK, the source code has been roughly seen, and then go back to the sample code to go through the process:
The flushSchedulerQueue was added to the task queue, and the cb of Watcher was called after the queue was flushed. That is, a “listen to mutation” output.