This is the 20th day of my participation in the More text Challenge. For more details, see more text Challenge

preface

Interviewer: tell me about the EventLoop. It’s called an EventLoop. You’ve heard of it.

I:… 💥 💥 💥 🚀

What is an EventLoop?

So let’s take a look at the picture, regardless of what the macro task is, what the microtask is, let’s look at the whole process.

Analysis:

  1. Determines whether the macro task queue is empty

    • No empty -> Execute the first queued task -> Go to the next step
    • If no, go to the next step
  2. Determines whether the microtask queue is empty

    • No empty -> Execute the task that entered the queue first -> continue to check whether the microtask queue is empty
    • If no, go to the next step

Because the first execution of the macro queue will have script (overall code block) tasks, so in fact, Js parsing is completed, in the asynchronous task, will first execute all microtasks, here is a lot of interview questions like to investigate. It should be noted that the newly created microtask is immediately queued for execution without waiting for the next cycle.

The main thread reads events from the task queue, and this process is repeated, so the whole mechanism is called an Event Loop.

Before diving into the event loop, a few concepts need to be understood:

  • Execution context
  • Execution stack
  • Micro-task
  • Macro -task

Execution context

Execution context is an abstract concept that can be understood as an environment in which code is executed. JS execution contexts are divided into three types: global execution context, function (local) execution context, and Eval execution context.

  • Global execution context: The global execution context refers to the window to which the global this refers, which can be an externally loaded JS file or code in a local tag.
  • Function execution contextFunction context is also called local context, and a new local context is created each time a function is called.
  • Eval execution context: This is uncommon and won’t be used here.

Execution stack

Execution stack is the “stack” in our data structure. It has the characteristic of “first in, then out”. Precisely because of this characteristic, when our code is executing, it will be pushed into the execution stack in turn when encountering an execution context.

When the code is executed, it first executes the code in the execution context at the top of the stack. When the execution context code at the top of the stack is finished, it leaves the stack and continues to execute the next execution context at the top of the stack.

function foo() {
    console.log('a');
    bar();
    console.log('b');
}
function bar() {
    console.log('c');
}
foo();
Copy the code
  1. Initialization state, executing stack task is empty.
  2. Function foo executes, foo enters the stack, prints a, and encounters function bar.
  3. Bar then enters the execution stack and starts executing the bar function, which outputs C.
  4. After the bar function finishes executing the stack, it continues to execute the function foo at the top of the stack, and finally prints b.
  5. Foo goes out of the stack, and all tasks in the execution stack are completed.

What are macrotasks and microtasks?

We all know that Js is single-threaded, but some time-consuming operations cause process blocking problems. To solve this problem, Js has two modes of execution for tasks: Synchronous and Asynchronous.

In asynchronous mode, there are two types of asynchronous tasks: macro task and micro task. In the ES6 specification,

  • Macrotasks are called tasks and microtasks are called Jobs.
  • Macro tasks are initiated by the host (browser, Node), while microtasks are initiated by JS itself.
Several ways to create macro and micro tasks 👇
Macrotask Microtask
setTimeout RequestAnimationFrame (disputed)
setInterval MutationObserver (browser environment)
MessageChannel Promise.[ then/catch/finally ]
I/O, event queue Process. nextTick (Node environment)
SetImmediate (Node Environment) queueMicrotask
Script (whole code block)

Note: The nextTick queue will execute before the Promie queue.

How do you understand that a script is a macro task 🤔

In fact, if two script blocks exist at the same time, the first script block will be synchronized first. If a microtask is created and entered into the microtask queue, the first script synchronized code will be emptied after execution. Turn on the execution of the second script block.

So it should be understandable why a script (an overall block of code) is a macro task.

With that in mind, how does the loop work? The following tasks are executed in the order of the function call stack.

  1. First, the event loop mechanism is fromscriptThe code inside the tag, which we mentioned above, the wholescriptThe tag is handled as a macro task.
  2. During code execution, if you encounter macro tasks such as:setTimeout, the current task will be distributed to the corresponding execution queue.
  3. During execution, if you encounter microtasks such as: PromiseIn creatingPromise When the object is instantiated, the code is executed sequentially, if it is executedThen,Operation, the task will be distributed to the microtask queue.
  4. script The code in the tag is executed, and the macro and microtasks involved in the execution are queued.
  5. At this point, the macro task is completed, and then it goes to the microtask queue to execute all existing microtasks.
  6. The microtask is executed, the first round of message loops is executed, and the page is rendered once.
  7. The second round of message loop is then started, fetching tasks from the macro task queue for execution.
  8. If no tasks are available in the two task queues, all tasks are completed.

The timersetTimeout

In addition to this, you can put a callback function to the timer that specifies how long it will take for certain code to execute.

There are two types of timers, setTimeout and setInterval. When we set the timer time to perform a specific task, as follows:

  // Execute after 1 second
  setTimeout(function () {
    console.log(2);
  }, 1000);
  console.log(1)


Copy the code

The output above is 1, 2, and after the synchronization code is executed, the task event in the timer is executed

  // Execute immediately after synchronous execution
  setTimeout(function () {
    console.log(2);
  }, 0);
  console.log(1)
Copy the code

When we execute the setTimeout (fn,0) timer, we place the scheduled task callback at the end of the task queue, which implies early execution.

This is to wait until the main step task and the “task queue” existing events are processed, and then the timer task will be executed immediately.

The premise is that if the current code has been running for a long time, there is no guarantee that the timer will execute at the specified time after the synchronization task and the task queue code have finished executing.

Note: The HTML5 standard specifies a minimum value (minimum interval) for the second argument of setTimeout(), which must not be less than 4 milliseconds. If it falls below this value, it will automatically increase.

If a page change is involved, this timer task is usually not executed immediately, but every 16 milliseconds, usually using requestAnimationFrame().

Little practical

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Message operation mechanism</title>
</head>

<body>
  
</body>
<script>
  console.log('1');
  setTimeout(() = > {
    console.log('2')},1000);
  new Promise((resolve, reject) = > {
    console.log('3');
    resolve();
    console.log('4');
  }).then(() = > {
    console.log('5');
  });
  console.log('6');/ / 1,3,4,6,5,2
</script>

</html>
Copy the code

Analysis:

  • Initialization state, execution stack empty.
  • Executed first<script>At this point, the global code enters the execution stack, synchronizes the sequence of execution code, output 1.
  • Asynchronous code encountered during executionSetTimeout (macro task)And assign it to the macro task asynchronous queue.
  • The synchronization code continues execution, encountering aPromise Asynchronous code (microtask). But the code in the constructor is synchronous code, output 3, 4 in sequence, thenthenSubsequent tasks are added to the microtask queue.
  • Finally, execute the synchronization code, output 6.
  • becausescript So this loop will process all asynchronous tasks in the microtask queue until all tasks in the microtask queue are completed. There is only one microtask in the microtask queue, so output 5.
  • At this point, the page will be rendered once, and after rendering, the next loop.
  • Fetch a macro task from the macro task queue, the previous one setTimeout, and output 2.
  • At this point, the task queue is empty, the execution stack is empty, and the entire program is executed.

This is a bit of a long story, so simplify it by following these steps:

  • The macro task (synchronous code in script) is executed at first, and when it is finished, the call stack is empty.
  • Then check whether there are any available tasks in the microtask queue and execute all the microtasks.
  • Render the page.
  • In the second round, a macro task is fetched from the macro task queue and executed, repeating the above loop.