Scenario, for example,
<template> <div> <div>{{number}}</div> <div @click="handleClick">click</div> </div> </template> export default { data () { return { number: 0 }; }, methods: { handleClick () { for(let i = 0; i < 1000; i++) { this.number++; }}}}Copy the code
When we press the Click button, the number is looped up 1000 times.
So, according to our previous understanding, every time number is +1, we fire the setter method for number, and we run all the way down to modifying the real DOM. The DOM will be updated 1000 times in the process! So what?
The nextTick function in vue. js passes in a cb, which is stored in a queue and triggers all cb events in the queue at the nextTick.
Vue.js is certainly not handled in such an inefficient way.
By default, every time a setter method for data is triggered, the corresponding Watcher object is actually pushed into a queue, On the next tick, queue all the way out and run the patch operation.
The nextTick method is not currently implemented in the browser platform, so vue. js uses Promise, setTimeout, and setImmediate to create an event in a microtask (or task). The goal is to execute the event after the current call stack finishes executing (not necessarily immediately).
export function nextTick (cb? : Function, ctx? : Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (! pending) { pending = true timerFunc() } // $flow-disable-line if (! cb && typeof Promise ! == 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }Copy the code
First define a callbacks array to store the nextTick, and all cb’s will be stored in this callbacks array until the nextTick processes these callbacks. Pending is a flag bit that represents a pending state.
SetTimeout will create an event called flushCallbacks in a task. FlushCallbacks will execute all the Cb’s in the callbacks in sequence.
watcher
In the example above, when we increase the number by 1000 times, it is correct to push the Watcher object into a queue and wait for the next tick to execute it. But did you notice that another problem arose?
Since the corresponding Watcher object is the same after number performs the ++ operation, we do not need to execute 1000 same Watcher objects to modify the interface on the next tick, but only need to execute one Watcher object. Make it change the 0 on the interface to 1000.
The same Watcher should be executed only once on the same tick, which means that there should be no duplicate Watcher objects in the queue.
So we can mark each Watcher object with an ID to make them look different.
Back to the first example, number is constantly ++, triggering the update method on its corresponding Watcher object in the Dep. And then you end up with a queue that filters Watcher objects with the same ID, so there’s actually only one Watcher object in the queue that corresponds to number. On the next tick (when the number has changed to 1000), the Watcher object’s run method is triggered to update the view, changing the number from 0 to 1000.