Introduction to the

About the execution context, execution stack, executive mechanism in the js task (synchronous and asynchronous tasks, micro and macro task task, event loop) is a high frequency test, in the interview asked some friend there may be a face of at a loss, so the author to conclude today, hoping to be helpful to the screen in front of you.

Threads and processes

Before we talk about the execution context and js execution mechanism, let’s talk about threads and processes

What is a thread

Threads are officially the smallest unit of CPU scheduling.

What is a process

Processes are officially the smallest unit of CPU resources allocated.

Relationship between threads and processes

A thread is a program running unit based on a process. A thread is an execution flow in a program. A process can have one or more threads.

Only one flow of execution in a process is called single-threaded, that is, when a program executes, the path of the program is arranged in sequential order, the first must be processed before the next can be executed.

Multiple execution streams in a process are called multithreading, which means that multiple threads can run simultaneously in a program to perform different tasks. That is, a single program is allowed to create multiple threads of parallel execution to complete their respective tasks.

The following author gives a simple example, for example, we open QQ music to listen to songs, QQ music can be understood as a process, in QQ music we can listen to songs while downloading here is multithreading, listening to songs is a thread, download is a thread. If we open up vscode again to write code this is another process.

Processes are independent of each other, but some resources are shared between threads in the same process.

The life cycle of a thread

The life cycle of a thread goes through five phases.

  1. New state: After a Thread object is created using the new keyword and the Thread class or its subclasses, the Thread object is in the new state. It remains in this state until the program starts () thread.

  2. Ready: When a thread object calls the start() method, the thread enters the ready state. A thread in a ready state is in a ready queue and can run as soon as it obtains CPU usage.

  3. Running state: If a ready thread acquires CPU resources, run() can be executed and the thread is running. The running thread is the most complex, and can be blocked, ready, or dead.

  4. Blocked: If a thread executes methods such as sleep, suspend, or wait and loses resources, it goes from running to blocked. You can re-enter the ready state when it is time to sleep or after obtaining device resources. It can be divided into three categories:

  • Wait to block: a thread in the running state executeswait()Method to put the thread into the wait to block state.
  • Synchronous blocking: the thread is fetchingsynchronizedThe synchronization lock failed (because the synchronization lock was occupied by another thread).
  • Other blocking: by calling threadsleep()join()issuedI/OWhen requested, the thread enters a blocked state. whensleep()Status timeout,join()Wait for the thread to terminate or time out, orI/OAfter processing, the thread returns to the ready state.
  1. Dead: When a running thread completes a task or other termination conditions occur, the thread switches to the terminated state.

Is JS single threaded or multi-threaded

JS is single threaded. As a browser scripting language, JS is mainly used to interact with users and manipulate DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. For example, if there are two threads of JavaScript at the same time, one thread adds content to a DOM node, and the other thread removes that node, which thread should the browser use?

Execution context and execution stack

What is an execution context

When the JS engine parses a snippet of executable code (usually in the function call phase), it performs some preparatory work prior to execution. This “preparatory work” is called an “execution context” or an execution environment.

Execution context classification

There are three types of execution context in javascript:

  1. Global execution Context This is the default or most basic execution context. There is only one global execution context in an application, and it will remain at the bottom of the execution stack for the entire life of the javascript script. The global context generates a global object (in the case of the browser environment, the global object is a window) and binds this value to the global object.

  2. Function execution Context Every time a function is called, a new function execution context is created (whether or not the function is called repeatedly).

  3. The Eval execution context Code executed inside the Eval function also has its own execution context, but since Eval is not used very often, we will not analyze it here.

What is an execution stack?

As mentioned earlier, the execution context is created at runtime, but the execution context needs to be stored, so how to store it? You need to use the stack data structure.

A stack is a fifo data structure.

So in summary, the execution context used to store code runtime creation is the execution stack.

Js execution process

When executing a piece of code, the JS engine first creates an execution stack, which holds the execution context.

Then the JS engine will create a global execution context and push it to the execution stack. In this process, the JS engine will allocate memory for all variables in the code and assign an initial value (undefined). After the creation, the JS engine will enter the execution phase, which will execute the code line by line. That is, assign values (true values) to variables that previously allocated memory.

If there is a function call in this code, the JS engine creates a function execution context and pushes it onto the execution stack, just like the global execution context.

When one execution stack completes, the execution context is popped out of the stack, and the next execution context is entered.

For example, let’s say we have the following code in our program

console.log("Global Execution Context start");

function first() {
  console.log("first function");
  second();
  console.log("Again first function");
}

function second() {
  console.log("second function");
}

first();
console.log("Global Execution Context end");
Copy the code

Let’s briefly analyze the above example

  1. First, an execution stack is created
  2. A global context is then created and the execution context is placedpushTo the execution stack
  3. Start executing, outputGlobal Execution Context start
  4. encounterfirstMethod to execute the method, create a function execution context andpushTo perform the stack
  5. performfirstExecute context, outputfirst function
  6. encountersecondMethod to execute the method, create a function execution context andpushTo perform the stack
  7. performsecondExecute context, outputsecond function
  8. secondWhen the execution context completes, it pops out of the stack and into the next execution contextfirstExecution context
  9. firstThe execution context continues execution, outputAgain first function
  10. firstWhen the execution context completes, it pops up from the stack and enters the next execution context, the global execution context
  11. The global execution context continues execution, outputGlobal Execution Context end

Let’s sum it up with a picture

All right. After the execution context and execution stack, we will talk about the execution mechanism of JS

Enforcement mechanism

When it comes to the execution mechanism of JS, we need to understand the synchronous task and asynchronous task, macro task and micro task in JS.

Synchronous and asynchronous tasks

In JS, tasks are divided into synchronous tasks and asynchronous tasks. What is synchronous task and what is asynchronous task?

A synchronous task refers to a task that is queued to be executed on the main thread. The next task can be executed only after the first task is completed.

Asynchronous tasks are tasks that do not enter the main thread but enter the “task queue” (tasks in the task queue are executed in parallel with the main thread) and only enter the main thread when the main thread is idle and the “task queue” notifies the main thread that an asynchronous task is ready to execute. Queue storage meets the fifO rule. Common asynchronous tasks include our setInterval, setTimeout, promise.then, etc.

Event loop

With the introduction of synchronous and asynchronous tasks, let’s talk about event loops.

  1. Synchronous and asynchronous tasks enter different execution “places” respectively. Synchronous tasks enter the main thread, and the latter task can be executed only after the former task is completed. Instead of entering the main thread, the asynchronous task enters the Event Table and registers the function.

  2. When the specified Event completes, the Event Table moves this function to the Event Queue. The Event Queue is a Queue data structure, so it satisfies the first-in, first-out rule.

  3. If the tasks in the main thread are empty after execution, the Event Queue will read the corresponding function and enter the main thread for execution.

This process is repeated over and over again, known as an Event Loop.

Let’s sum it up with a picture

The following author will briefly introduce an example

function test1() {
  console.log("log1");

  setTimeout(() = > {
    console.log("setTimeout 1000");
  }, 1000);

  setTimeout(() = > {
    console.log("setTimeout 100");
  }, 100);

  console.log("log2");
}

test1(); // log1, log2, setTimeout 100, setTimeout 1000
Copy the code
  1. We know that synchronous tasks are executed before asynchronous tasks in JS, so the above example is printed firstLog1, log2
  2. After a synchronous task is executed, an asynchronous task will be executed100The millisecond callback takes precedence over the outputsetTimeout 100
  3. delay1000The milliseconds callback function executes the outputsetTimeout 1000

The above example is relatively simple, I believe that as long as you understand the above author said that the synchronous asynchronous task is no problem. Let me give you another example. What does it output?

function test2() {
  console.log("log1");

  setTimeout(() = > {
    console.log("setTimeout 1000");
  }, 1000);

  setTimeout(() = > {
    console.log("setTimeout 100");
  }, 100);

  new Promise((resolve, reject) = > {
    console.log("new promise");
    resolve();
  }).then(() = > {
    console.log("promise.then");
  });

  console.log("log2");
}

test2();
Copy the code

To solve the above problem, it is not enough to know synchronous and asynchronous tasks; we also need to know macro and micro tasks.

Macro and micro tasks

In JS, tasks are divided into two types, one is called MacroTask and the other is called MicroTask.

Common macro tasks are MacroTask

  1. The main block of code
  2. setTimeout()
  3. setInterval()
  4. setImmediate() – Node
  5. RequestAnimationFrame () – Browser

Common microtasks are microtasks

  1. Promise.then()
  2. process.nextTick() – Node

So in the above example, macro tasks and micro tasks are involved. What is the execution sequence of macro tasks and micro tasks?

  1. First of all, when the whole script(as the first macro task) starts to execute, all the code will be divided into synchronous tasks and asynchronous tasks. Synchronous tasks will be directly executed in the main thread, and asynchronous tasks will be entered into the asynchronous queue and then divided into macro tasks and micro tasks.

  2. The macro task enters the Event Table and registers the callback function in the Event Table, which will be moved to the Event Queue whenever the specified Event completes

  3. The microtask will also go to another Event Table and register a callback function in it, which will be moved to the Event Queue whenever the specified Event completes

  4. When tasks in the main thread are completed and the main thread is empty, the Event Queue of microtasks will be checked. If there are tasks, they will all be executed. If there are no tasks, the next macro task will be executed

Let’s sum it up with a picture

After reading the macro and micro tasks in async, we can easily get the answer.

  1. We know that synchronous tasks are executed before asynchronous tasks in JS, so the above example is printed firstLog1, new Promise, log2. Notice hereThe new promise is synchronized
  2. The main code block, after executing as a macro task, executes all the microtasks generated by the macro task, so it printspromise.then
  3. After all the microtasks are completed, a macro task will be executed, which is delayed100The millisecond callback takes precedence over the outputsetTimeout 100
  4. This macro task does not produce microtasks, so there are no microtasks to execute
  5. Proceed to the next macro task, delay1000The millisecond callback function optimizes the outputsetTimeout 1000

So the test2 method will output log1, new PROMISE, log2, Promise. then, setTimeout 100, setTimeout 1000

Js execution in the end is the first macro task and then micro task or first micro task and then macro task online articles have said. The author’s understanding is that if the whole JS code block as a macro task when our JS execution order is the first macro task after the micro task.

Is the so-called hundred see is better than one practice, the following I give two examples if you can do it right you can master the JS implementation mechanism of this piece of knowledge.

Example 1

function test3() {
  console.log(1);

  setTimeout(function () {
    console.log(2);
    new Promise(function (resolve) {
      console.log(3);
      resolve();
    }).then(function () {
      console.log(4);
    });
    console.log(5);
  }, 1000);

  new Promise(function (resolve) {
    console.log(6);
    resolve();
  }).then(function () {
    console.log(7);
    setTimeout(function () {
      console.log(8);
    });
  });

  setTimeout(function () {
    console.log(9);
    new Promise(function (resolve) {
      console.log(10);
      resolve();
    }).then(function () {
      console.log(11);
    });
  }, 100);

  console.log(12);
}

test3();
Copy the code

Let’s look at it in detail

  1. First of all,jsThe entire code block is executed initially as a macro task and output sequentiallyOne, six, twelve.
  2. After the macro task of the whole code block is executed, one microtask and two macro tasks are generated, so the macro task queue has two macro tasks and the microtask queue has one microtask.
  3. After the macro task is executed, all microtasks generated by the macro task are executed. Because there is only one microtask, it will output7. This microtask generates another macro task, so the macro task queue currently has three macro tasks.
  4. None of the three macro tasks is set to delay the first execution, so output8, this macro task did not produce a microtask, so there are no microtasks to execute, proceed to the next macro task.
  5. delay100Ms macro task execution, output9, 10, and produces a microtask, so the microtask queue currently has a microtask
  6. After the execution of the macro task, all the microtasks generated by the macro task will be executed. Therefore, all the microtasks in the microtask queue will be executed and output11
  7. delay1000Macro task execution output in millisecondsTwo, three, five, and produces a microtask, so the microtask queue currently has a microtask
  8. After the execution of the macro task, all the microtasks generated by the macro task will be executed. Therefore, all the microtasks in the microtask queue will be executed and output4

So the above code example prints 1, 6, 12, 7, 8, 9, 10, 11, 2, 3, 5, 4, are you doing it correctly?

Example 2

Let’s modify example 1 above slightly to introduce async and await

async function test4() {
  console.log(1);

  setTimeout(function () {
    console.log(2);
    new Promise(function (resolve) {
      console.log(3);
      resolve();
    }).then(function () {
      console.log(4);
    });
    console.log(5);
  }, 1000);

  new Promise(function (resolve) {
    console.log(6);
    resolve();
  }).then(function () {
    console.log(7);
    setTimeout(function () {
      console.log(8);
    });
  });

  const result = await async1();
  console.log(result);

  setTimeout(function () {
    console.log(9);
    new Promise(function (resolve) {
      console.log(10);
      resolve();
    }).then(function () {
      console.log(11);
    });
  }, 100);

  console.log(12);
}

async function async1() {
  console.log(13)
  return Promise.resolve("Promise.resolve");
}

test4();
Copy the code

So what is the output of this example up here? We can say async and await.

We know that async and await are syntactic candy of Promise. We just need to know that await is equivalent to promise.then. So the above example can be interpreted as the following code

function test4() {
  console.log(1);

  setTimeout(function () {
    console.log(2);
    new Promise(function (resolve) {
      console.log(3);
      resolve();
    }).then(function () {
      console.log(4);
    });
    console.log(5);
  }, 1000);

  new Promise(function (resolve) {
    console.log(6);
    resolve();
  }).then(function () {
    console.log(7);
    setTimeout(function () {
      console.log(8);
    });
  });

  new Promise(function (resolve) {
    console.log(13);
    return resolve("Promise.resolve");
  }).then((result) = > {
    console.log(result);

    setTimeout(function () {
      console.log(9);
      new Promise(function (resolve) {
        console.log(10);
        resolve();
      }).then(function () {
        console.log(11);
      });
    }, 100);

    console.log(12);
  });
}

test4();
Copy the code

Is it easy to see the code above?

  1. First of all,jsThe entire code block is executed initially as a macro task and output sequentiallyOne, six, thirteen.
  2. After the macro task of the whole code block is executed, two microtasks and one macro task are generated, so the macro task queue has one macro task and the microtask queue has two microtasks.
  3. After the macro task is executed, all microtasks generated by the macro task are executed. So it will output7, Promise. Resolve, 12. This microtask generates two more macro tasks, so the macro task queue currently has three macro tasks.
  4. None of the three macro tasks is set to delay the first execution, so output8, this macro task did not produce a microtask, so there are no microtasks to execute, proceed to the next macro task.
  5. delay100Ms macro task execution, output9, 10, and produces a microtask, so the microtask queue currently has a microtask
  6. After the execution of the macro task, all the microtasks generated by the macro task will be executed. Therefore, all the microtasks in the microtask queue will be executed and output11
  7. delay1000Macro task execution output in millisecondsTwo, three, five, and produces a microtask, so the microtask queue currently has a microtask
  8. After the execution of the macro task, all the microtasks generated by the macro task will be executed. Therefore, all the microtasks in the microtask queue will be executed and output4

Resolve, 12, 8, 9, 10, 11, 2, 3, 5, 4.

extension

setTimeout(fn, 0)

SetTimeout (fn) should be executed immediately.

SetTimeout (fn), setTimeout(fn,0), is the same thing.

SetTimeout (fn) is an asynchronous task, so even if you do not set the delay time, it will enter the asynchronous queue and wait until the main thread is idle.

Do you think the delay time we set after setTimeout will be executed according to our delay time? I don’t think so. The time we set is when the callback function can be executed, but whether the main thread is empty is another matter. We can use a simple example.

function test5() {
  setTimeout(function () {
    console.log("setTimeout");
  }, 100);

  let i = 0;
  while (true) {
    i++;
  }
}

test5();
Copy the code

Does the above example necessarily print setTimeout after 100 milliseconds? No, because our main thread is in an infinite loop and there is no time to execute tasks in the asynchronous queue.

GUI rendering

GUI rendering here said that some friends may not understand, the author will be on the browser article will be introduced in detail, here is just a simple understanding.

As the JS engine thread and GUI rendering thread are mutually exclusive, in order to make macro tasks and DOM tasks orderly, the browser will start rendering the page after the execution of one macro task and before the execution of the next macro task.

So the relationship between macro tasks, micro tasks, and GUI rendering is as follows

Macro Tasks -> Micro Tasks -> GUI Rendering -> Macro tasks ->...Copy the code

Refer to the article

Get to know how JS works at once

This time, thoroughly understand the JavaScript execution mechanism

Afterword.

This article is the author’s personal study notes, if there are fallacies, please inform, thank you! If this article has been helpful to you, please click the “like” button. Your support is my motivation to keep updating.