NextTick: MutationObserver is just a cloud, microTask is the core!

Read the big guy’s article, the first time to remember, after a period of time to recall the discovery and forget, write an article to record the learning process, as well as simple implementation of a nextTick.

The core of this article is to correct a common mistake by first asking: why is it necessary to use $nextTick to get the changed DOM value after modifying data in Vue 👇 stamp here

export default {
    data() {
        return {
                inputVal: 1}},mounted() {
        this.inputVal = 2
        console.log(document.getElementById('input').value) / / 1
        this.$nextTick(() = > {
                console.log(documen.getElementById('input').value) / / 2})},render() {
        return h('input', {
                attrs: {
                        id: 'input'
                },
                props: {
                        value: this.inputVal
                }
        })
    }	
}
Copy the code

Question 1: Are DOM operations synchronous or asynchronous?

It’s synchronous!!

DOM manipulation is synchronous, in essence, the DOM is just an object, the synchronization here refers to the modify the DOM object, the data level, you can get it next line of code changes as a result, but may not be on the vision, because if you want to see on the page apply colours to a drawing gives the corresponding element, also need a browser to Render the operation, Render is asynchronous and should not be confused.

Since DOM operations are synchronous, why does Vue need nextTick to get the changed element values?

Because Vue operates on the DOM asynchronously!!

In this code, Vue performs DOM operations on input as follows:

This. InputVal = 2 -> add render Watcher to queue -> call nextTick(flushSchedulerQueue) to flush Watcher task queue 🌟 asynchronous 🌟 -> Trigger the render Watcher, changing the DOM

mounted() {
    this.inputVal = 2   // Operations on the DOM do not take effect until the next tick
    console.log(document.getElementById('input').value) / / 1
    this.$nextTick(() = > {
            console.log(document.getElementById('input').value) / / 2})},Copy the code

Vue uses nextTick to schedule tasks. Essentially, it starts a microtask. This.$nextTick saves the callbacks passed in, but instead of executing them immediately, it queues them for execution on the nextTick

Problem 3: Simply implement a nextTick

Combined with source code, simplify some logic, to achieve a simple nextTick, its principle is also very simple, call nextTick cb save,

// next-tick.js
const callbacks = [];
let pending = false;

// Execute all callbacks
function flashCallbacks() {
     pending = false;
    // Assign a callback to the next task queue if nextTick is called within nextTick
    let copies = callbacks.slice(0)

    for(let i=0; i<copies.length; i++) { copies[i](); }}let timerFunc;

// timerFunc is the actual microtask that needs to be performed
// TODO:Compatibility degradation: Promise -> mutationObserver -> setImmediate -> setTimeout
 let p = Promise.resolve();
 timerFunc = () = > {
   p.then(() = > {
     flashCallbacks();
   });
 };

// nextTick
export function nextTick(cb, ctx) {
  callbacks.push(() = > {
      cb.call(ctx);
  });
  if(! pending) { pending =true; timerFunc(); }}Copy the code

Question 4: What’s the difference between a nextTick and a regular Promise?

Each chain-call then of a Promise opens a new microtask, while nextTick executes in the same tick, adding all callbacks to the same microtask. Let’s look at an example

// test.js
import { nextTick } from 'next-tick.js'
nextTick(() = > {
  console.log(1);
  nextTick(() = > {
    console.log(4);
  });
});

Promise.resolve()
  .then(() = > {
    console.log(3);
  })
  .then(() = > {
    console.log(6);
  });

nextTick(() = > {
  console.log(2);
  nextTick(() = > {
    console.log(5);
  });
});

Copy the code

The final output is

1
2
3
4
5
6
Copy the code