This is the fifth day of my participation in the August Wen Challenge.More challenges in August

Case analysis

Get the updated DOM

It is common to change a DOM and then read the value of the DOM with vUE, but the result is often not what we expected due to the vUE’s asynchronous updating mechanism, such as this case:

<p id="msg">{{message}}</p>

data: {
    message: '123465',}clickBtn() {
    this.message = 'hello'
    let msg = document.querySelector('#msg').innerText // Message is reassigned, expecting hello
    console.log(msg) // Actual output 123465
}
      
Copy the code

How to solve it? Using nextTick

clickBtn(){
    this.message = 'hello'
    this.nextTick(() = >{
        let msg = document.querySelector('#msg').innerText // Message is reassigned, expecting hello
        console.log(msg) // Actually prints hello})}Copy the code

Source code analysis

Instead of immediately updating the view when it hears data changes, it starts a queue to buffer the Watcher, triggers the same Watcher multiple times, pushes the Watcher to the queue only once, and finally calls nextTick to refresh the queue to execute the Watcher

update

Vue decides to update according to different conditions. In general, I adopts an asynchronous update strategy, traversing the subs property of the subscriber Dep object, which contains the list of Watcher. In asynchronous update, queueWatcher() is used to put the Watcher into an update queue

// core/observe/watcher.js
update() {
    /* istanbul ignore else */
    if (this.lazy) {
        / / lazy loading
        this.dirty = true
    } else if (this.sync) {
        // Synchronize execution
        this.run()
    } else {
        // Execute asynchronously
        queueWatcher(this) // Put the current Watcher into the update queue}}Copy the code

queueWatcher

The Watcher is placed in the queue property in an orderly manner, and only one Watcher can be updated at a time

// core/observe/scheduler.js
export function queueWatcher(watcher: Watcher) {
    const id = watcher.id
    // Assign weight according to id,
    if (has[id] == null) {

        has[id] = true
        if(! flushing) {// Not flushing the Watcher queue
            queue.push(watcher) // Push the Watcher to the queue. There is only one Watcher with the same ID
        } else {
            // It has been updated, put this watcher next to the current watcher, and execute the inserted watcher as soon as the current watcher completes execution
            let i = queue.length - 1
            while (i > index && queue[i].id > watcher.id) {
                i--
            }
            queue.splice(i + 1.0, watcher)
        }
        // queue the flush
        // While nextTick is locked, watcher can be pushed to queue
        // Wait until the flushSchedulerQueue executes to unlock the flushSchedulerQueue and restore the state for the next nextTick
        if(! waiting) { waiting =true
            if(process.env.NODE_ENV ! = ='production' && !config.async) {
                // Not asynchronous refresh
                flushSchedulerQueue() // Update the queue immediately
                return
            }
            nextTick(flushSchedulerQueue) // Update asynchronously}}}Copy the code

flushSchedulerQueue

The watcher.run() function iterates through the queue to be updated and executes watcher.run() on each Watcher in the queue.

// core/observe/scheduler.js
function flushSchedulerQueue() {
  currentFlushTimestamp = getNow() // Get the current timestamp
  flushing = true // Set to refresh
  let watcher, id

  // Component updates from parent to child. (Because the parent is always created before the child node)
  // The component's user monitor runs before its render monitor (because the user observer is created before the render observer)
  // If a component is destroyed while the parent's monitor is running, its observer can be skipped.

  queue.sort((a, b) = > a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index] / / remove the Watcher
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run() // Update the Watcher in the queue
    // in dev build, check and stop circular updates.
    // Update up to 100 times to prevent infinite loops
    if(process.env.NODE_ENV ! = ='production'&& has[id] ! =null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' +
            (watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`),
          watcher.vm
        )
        break}}}// keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice() // Copy the active component
  const updatedQueue = queue.slice() // Copy the updated queue

  resetSchedulerState() // Resets the state of the refresh queue for the next refresh

  // Call Component updated and activated hooks // Lifecycle hook functions
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')}}Copy the code

vm.nextTick

Push the callback function into the Callbacks array and cache it

// core/util/next-tick.js
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)
    }
  })
     // Lock timerFunc, call nextTick multiple times, and place cb callbacks directly into the Callbacks array
  // timerFunc is executed after all nextTick is executed
  if(! pending) { pending =true
    timerFunc() // How do callback letters work, Promise? SetTimeout method? MutationObserver way? SetImmediate way? Compatible with various environments
  }
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') {
    // Simply return a Promise object if no callback function is returned
    return new Promise((resolve) = > {
      _resolve = resolve
    })
  }
}

Copy the code

timerFunc

Finally, the timerFunc() function iterates through the callbacks cache and executes, mainly deciding how to refresh the callback: Promise, setTimeout, and so on. To take just one example,

// core/util/next-tick.js
let timerFunc = () = > {
    setTimeout(flushCallbacks, 0)}function flushCallbacks() {
  pending = false // Clear the flag
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]() Execute the function in callbacks that nextTick carries with it}}Copy the code

conclusion

The general process of asynchronous rendering: Trigger multiple Watcher –> Enter the ququeWatcher–> reassign by ID –> push to queue –> Trigger nextTick–> push the flushSchedulerQueue into the callbacks with closure –> enter timerFunc to iterate callbacks The function of

Always remembernextTickAsynchronous operation

clickBtn() {
    this.$nextTick(() = > {
        console.log('nextTick1')})console.log(2)
    this.$nextTick(() = > {
        console.log('nextTick2')})console.log(1)}// Output: 2 1 nextTick1 nextTick2

Copy the code

For example, now operate continuously on a property this. MSG =1; This. MSG =2vue Instead of updating this property twice in a row, as might be expected, only perform the last time according to the asynchronous rendering mechanism to avoid meaningless operation loss of performance

In addition, using the nextTick method multiple times in a row does not immediately execute the contents of nextTick, which is rare. Reading the source code above, it caches the nextTick callback into the callbacks array, and finally executes the operations through the number

clickBtn(){
    this.$nextTick(() = > {
        console.log('nextTick1')})this.$nextTick(() = > {
        console.log('nextTick2')})}Copy the code

The articles

  • [Vue source]–$nextTick and asynchronous rendering (line by line comments)
  • [Vue source]–mixin and extend (line by line)
  • [Vue source]–Diff algorithm (line by line comment)
  • [Vue source]– How to listen for array changes (line by line comment)
  • [Vue source]– Reactive (bidirectional binding) principle (line by line comment)
  • [Vue source]– what is done for each lifecycle (line by line comments)
  • [Vue source]– How to generate virtual DOM (line by line comment)