1. Why does VUE use asynchronous updates

Vue framework is sensitive to data changes, and it can know exactly which data changes, so VUE can control the update granularity. In VUe1, it is fine-grained update (DOM update in a small range), so it occupies high memory and consumes large performance. After vue2 change the update for medium strength, positioning at the component level, a component contains a large number of state, in an event loop cycle, regardless of the state of the components change how many or how many times a state change, only a UI update, also is only performed a component rendering function, asynchronous to deal with. In VUe2 component updates are made using the Watcher function, while in VUe3 they are made using the Effect side effect function.
Effect functions are divided into three types:
  1. The effect function of a component, used for updating and rendering components. When the state changes, the effect function is pushed into the microqueue and de-duplicated, so that no matter how many times the state changes within the component, it is executed only once.
  2. The effect function, which evaluates attributes, is lazy and only executes when the dependent value changes. Otherwise, the result of the last calculation is always used, which has a caching effect.
  3. The effect function of watch is used to monitor the change of a certain state and then do the corresponding logical processing.

2. Vue3 implements the logic of asynchronous queues

  1. Vue realizes asynchronous update by using microtask. When the status changes, the component Effect rendering function is pushed into the task queue. When the microtask is executed, the effect function collected in the task queue is executed to change the DOM.
  2. Vue3 asynchronous update task queues are divided into three types: 1. Update asynchronous queue queue 2. PendingPreFlushCbs 3. PendingPostFlushCbs Postupdate task queue after the asynchronous queue. PendingPreFlushCbs, Queue, and pendingPostFlushCbs queue tasks are executed in sequence during microtask execution
  3. Each time a task is added to the pendingPreFlushCbs, Queue, and pendingPostFlushCbs queues, the queueFlush function is executed to create a microtask and, if it already exists, to take advantage of it.

Vue3 asynchronous task code implementation

1. Initialize various states and create task queues and microtasks
// Identifies whether a microtask function is being executed
let isFlushing = false;
// Identifies whether the microtask function is waiting to execute
let isFlushPending = false;

// Update queue asynchronously
const queue = [];
// The current queue task sequence
let flushIndex = 0;


// Asynchronously update the front queue
let pendingPreFlushCbs = [];
let activePreFlushCbs = null;
// Sequence of pre-task execution
let preflushIndex = 0;

// Asynchronously update the post-queue
let pendingPostFlushCbs = [];
// Asynchronous update postqueue Queues that are currently executing, distinguished because new tasks may be added during execution
let activePostFlushCbs = null;
// The sequence of tasks executed after the task queue
let postflushIndex = 0;


// Add a microtask method
let resolvedPromise = Promise.resolve();
// represents the promise being implemented
let currentFlushPromise;
Copy the code

Create a queue, pendingPreFlushCbs, pendingPostFlushCbs queue, FlushIndex, preflushIndex, and postflushIndex are used to record the execution positions of asynchronous task queues, preflushIndex, and postflushIndex. IsFlushing is used to record whether the task queue is being executed. IsFlushPending Is used to identify whether the task queue is waiting to execute. A resolvedPromise is a promise used to push a function into a microtask queue.

2. Create functions to push tasks into queues, pendingPreFlushCbs, and pendingPostFlushCbs
/ /...
// omit the previous code

// Join the asynchronous task queue to process tasks
export const queueJob = (job) = > {
    // The current task is added to the queue only when it is not in the queue
    if(! queue.length || ! queue.includes(job)) { queue.push(job);// Perform microtasksqueueFlush(); }}// Public logic for handling prev and POST
PendingQueue Specifies the queue of tasks waiting to be executed
const queueCb = (job, activeQueue, pendingQueue) = > {

    if(! activeQueue || ! activeQueue.includes(job)) { pendingQueue.push(job)// Perform microtasks
        queueFlush()
    }
}
// Add tasks to the task queue
export const queuePreFlushCb = (job) = > {
    queueCb(job, activePreFlushCbs, pendingPreFlushCbs)
}
// Post queue processing
export const queuePostFlushCb = (job) = > {
    queueCb(job, activePostFlushCbs, pendingPostFlushCbs)
}
Copy the code
  1. When queueJob is executed to add a task to the queue, check whether the task to be added exists in the queue. If the task is not in the queue, the task is pushed to the queueJob queue to achieve deduplication. Therefore, within one event cycle, Interface rendering is performed only once, regardless of how many states change within a component or how many times a state changes (the component effect rendering function is the same for all states within a component).

2. Copy the activePreFlushCbs and activePostFlushCbs task queues when the pendingPreFlushCbs and pendingPostFlushCbs task queues are executed. Then empty the pendingPreFlushCbs and pendingPostFlushCbs queues. Therefore, when running queuePreFlushCb or queuePostFlushCb push tasks, you need to check whether the activePreFlushCbs and activePostFlushCbs tasks exist. If the current pushed task does not exist in activePreFlushCbs or activePostFlushCbs, the current task is stored in pendingPreFlushCbs or pendingPostFlushCbs.

3. Each time a user pushes a task to the task queue, queueFlush is executed to create a microtask and wait for the task queue to execute
// Execute queue
const queueFlush = function () {
    // A task is added to a microtask only if no microtask is executed to refresh the task queue
    FlushJobs flushJobs flushJobs flushJobs flushJobs flushJobs flushJobs flushJobs flushJobs flushJobs
    if(! isFlushing && ! isFlushPending) { isFlushPending =true;
        currentFlushPromise = resolvedPromise.then(flushJobs)
    }
}
Copy the code

When isFlushing and isFlushPending are both false, the microtask is not yet available, and the flushJobs need to be created and pushed to the microtask queue for execution. Then set isFlushPending to true. When the user pushes a task to the task queue within this event cycle, no new microtasks need to be created

4. The task queue execution function flushJobs is implemented
  1. When the flushJobs function is executed, the state is first changed to indicate that the task queue is currently executing
const flushJobs = () = > {
    isFlushing = true;
    isFlushPending = false;
 }
Copy the code
  1. Run the flushPostFlushCbs function to clear the flush task queue
const flushJobs = () = > {
    isFlushing = true;
    isFlushPending = false;
    
    // Execute the front-queue task
    flushPreFlushCbs();
 }
 // Refresh the front queue
const flushPreFlushCbs = () = > {
    // Assign the pendingPostFlushCbs that needs to be executed to activePreFlushCbs and empty pendingPostFlushCbs
    if (pendingPreFlushCbs.length) {

        activePreFlushCbs = [...new Set(pendingPreFlushCbs)];
        pendingPreFlushCbs.length = 0;


        for (preflushIndex; preflushIndex < activePreFlushCbs.length; preflushIndex++) {
            const task = activePreFlushCbs[preflushIndex];
            task();
        }
        activePreFlushCbs = null;
        preflushIndex = 0;
        // Wait until the queue is empty, then proceed to the asynchronous update queueflushPreFlushCbs(); }}Copy the code

The flushPreFlushCbs function first checks whether there are tasks in the flush queue. If there are tasks in the flush queue, copy the tasks in the pendingPreFlushCbs to the activePreFlushCbs. The pendingPreFlushCbs queue is then flushed, and the tasks in the activePreFlushCbs queue are executed sequentially. Finally, execute flushPreFlushCbs recursively until pendingPreFlushCbs empties all tasks

3. Clear the task queue

const flushJobs = () = > {
    isFlushing = true;
    isFlushPending = false;
    // Execute the front-queue task
    flushPreFlushCbs();

    / asynchronous queue tasks * * * because of the asynchronous task queue is mostly component rendering function, so is my father child components after execution component * parent component to create effect of id is less than the child component id so arrangement and for execution order after my father since the childhood * so here to sort sort queue * * /
    queue.sort((a, b) = > getId(a) - getId(b));
    try {
        // Execute the queue task once
        for(flushIndex; flushIndex < queue.length; flushIndex++) { callWithErrorHandling(queue[flushIndex]); }}finally {
        flushIndex = 0;
        queue.length = 0; }}// Use to compare size sort
const getId = (job) = > {
    return job.id == null ? Infinity : job.id;
}
Copy the code

First, we need to sort queue from smallest to largest, because queue collects the effect rendering function of components. Component rendering needs to be executed in order from parent to child. The effect function of the parent component is created before the child component, so the effect function ID of the parent component is smaller. Sorting from smallest to largest ensures that the parent component executes before the child component

  1. Clear the pendingPostFlushCbs task queue
const flushJobs = () = > {
    / /...
     / /...
      / /...
    try{}finally {
         / /...
         / /...
        // Execute a post-queue task
        flushPostFlushCbs()
        // As long as the asynchronous and post-task queues have tasks, they will continue to execute until the queue is empty
        if (queue.length || pendingPostFlushCbs.length) {
            flushJobs();
        }
        currentFlushPromise = null; }}// Refresh the back queue
const flushPostFlushCbs = () = > {
    let deps = [...new Set(pendingPostFlushCbs)];
    pendingPostFlushCbs.length = 0;
   
    if (activePostFlushCbs) {
        returnactivePostFlushCbs.push(... deps); } activePostFlushCbs = deps;/ / sorting
    activePostFlushCbs.sort((a, b) = > getId(a) - getId(b));
    / / execution
    for (postflushIndex = 0; postflushIndex < activePostFlushCbs.length; postflushIndex++) {
        const task = activePostFlushCbs[postflushIndex];
        task();
    }
    activePostFlushCbs = null;
    postflushIndex = 0;
}
Copy the code

If the queue and pendingPostFlushCbs queues still have tasks, flushJobs is recursively executed until the queue is empty

3. Making the address

The code links to the miniVue3 folder for a simple implementation of vue3 code