How to quickly get started with VUE3.0: Learn
about the execution context, execution stack, and execution mechanism (synchronous tasks, asynchronous tasks, microtasks, macrotasks, and event loops) in js
It is a frequent test point in interviews, and some friends are You may feel confused when asked, so I will summarize it today, hoping it can be helpful to you in front of the screen.
Before talking about the execution context and js
execution mechanism in js
, let's talk about threads and processes.
In official terms,线程
is the smallest unit of CPU
scheduling.
? In official terms,进程
is the smallest unit of CPU
resource allocation.
线程
is a program running unit based on进程
. In layman's terms,线程
is an execution flow in a program. A进程
can have one or more线程
.
There is only one execution flow in a进程
called单线程
. That is, when the program is executed, the program paths taken are arranged in consecutive order. The previous ones must be processed before the later ones will be executed.
Multiple execution streams in a进程
are called多线程
, that is, multiple different线程
can be run simultaneously in a program to perform different tasks, which means that a single program is allowed to create multiple parallel execution线程
to complete their respective tasks. .
The author will give a simple example below. For example, if we open qq音乐
and listen to songs, qq音乐
can be understood as a process. In qq音乐
we can download while listening to songs. This is multi-threading. Listening to songs is a thread, and downloading is a process. thread. If we open vscode
again to write code, it will be another process.
Processes are independent of each other, but some resources are shared between threads under the same process.
The life cycle of a thread goes through five stages.
New state: After using the new
keyword and Thread
class or its subclass to create a thread object, the thread object is in the new state. It remains in this state until the program start()
the thread.
Ready state: When the thread object calls start()
method, the thread enters the ready state. The thread in the ready state is in the ready queue and can be run immediately as long as it obtains the right to use CPU
.
Running state: If the thread in the ready state obtains CPU
resources, it can execute run()
, and the thread is in the running state. The thread in the running state is the most complex, it can become blocked, ready and dead.
Blocking state: If a thread executes sleep(睡眠)
, suspend(挂起)
, wait(等待)
and other methods, after losing the occupied resources, the thread will enter the blocking state from the running state. The ready state can be re-entered after the sleep time has expired or device resources have been obtained. It can be divided into three types:
waiting blocking: the thread in the running state executes wait()
method, causing the thread to enter the waiting blocking state.
Synchronous blocking: The thread fails to acquire the synchronized
synchronization lock (because the synchronization lock is occupied by other threads).
Other blocking: When I/O
request is issued by calling the thread's sleep()
or join()
, the thread will enter the blocking state. When sleep()
state times out, join()
waits for the thread to terminate or times out, or the I/O
processing is completed, and the thread returns to the ready state.
Death state: When a running thread completes its task or other termination conditions occur, the thread switches to the terminated state.
JS
is single-threaded. As a browser scripting language, JS
is mainly used to interact with users and operate DOM
. This determines that it can only be single-threaded, otherwise it will cause very complex synchronization problems. For example, suppose JavaScript
has two threads at the same time. One thread adds content to a certain DOM
node, and the other thread deletes the node. In this case, which thread should the browser use?
When JS
engine parses an executable code fragment (usually the function call phase), it will first do some preparatory work before execution. This "preparation work" is called "execution context ." (Execution context (referred to as EC
)" or it can also be called execution environment .
There are three execution context types in javascript
, which are:
global execution context . This is the default or most basic execution context. There will only be one global context in a program, and it will exist throughout the life cycle of the javascript
script. The bottom of the execution stack will not be destroyed by stack popping. The global context will generate a global object (taking the browser environment as an example, this global object is window
), and bind the this
value to this global object.
Function Execution Context Whenever a function is called, a new function execution context is created (regardless of whether the function is called repeatedly).
Eval function execution context The code executed inside eval
function will also have its own execution context, but since eval
is not often used, it will not be analyzed here.
Earlier we mentioned that js
will create an execution context when it is running, but the execution context needs to be stored, so what is used to store it? You need to use the stack data structure.
The stack is a first-in-last-out data structure.
So in summary, the execution context used to store the execution context created when the code is running is the execution stack .
When executing a piece of code JS
Then JS
engine will create a global execution context and push
to the execution stack. In this process, JS
engine will allocate memory for all variables in this code and assign an initial value (undefined). After the creation is completed, the JS
engine will enter execution. stage, in this process JS
engine will execute the code line by line, that is, assign values (real values) to the variables that have been allocated memory one by one.
If there is function
call in this code, JS
engine will create a function execution context and push
to the execution stack. The creation and execution process is the same as the global execution context.
When an execution stack is completed, the execution context will be popped from the stack, and then the next execution context will be entered.
Let me give an example below. If 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");
Let's briefly analyze the above example.
First, an execution stack will be created
, then a global context will be created, and the execution context will push
to the execution stack
to start execution, and Global Execution Context start
will be output. Global Execution Context start
encounters first
method, executes the method, creates a function execution context and push
to the execution stack
to execute first
execution context, outputs first function
encounters second
method, executes the method, creates a function execution context and push
to the execution stack to
execute second
execution Context, output second function
second
execution context has been executed, popped from the stack, and entered the next execution context. first
execution context,
first
execution context continues execution, output Again first function
first
execution context has been executed, popped from the stack, and entered the next Execution context Global execution context
Global execution context Continue execution and output Global Execution Context end
We use a picture to summarize
alright. After talking about the execution context and execution stack, let’s talk about the execution mechanism of js. Speaking of the execution mechanism of js
js
need to understand the synchronous tasks, asynchronous tasks, macro tasks and micro tasks in js
.
In js
, tasks are divided into synchronous tasks and asynchronous tasks. So what are synchronous tasks and what are asynchronous tasks?
Synchronous tasks refer to tasks queued for execution on the main thread. The next task can only be executed after the previous task has been executed.
Asynchronous tasks refer to 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). Only when the main thread is idle and the "task queue" notifies the main thread, an asynchronous task Once it can be executed, the task will enter the main thread for execution. Because it is queue storage, it meets the first-in-first-out rule . Common asynchronous tasks include our setInterval
, setTimeout
, promise.then
, etc.
has previously introduced synchronous tasks and asynchronous tasks. Now let’s talk about the event loop.
Synchronous and asynchronous tasks enter different execution "places" respectively, and enter the main thread synchronously. Only when the previous task is completed can the next task be executed. Asynchronous tasks do not enter the main thread but enter Event Table
and register functions.
When the specified thing is completed, Event Table
will move this function into Event Queue
. Event Queue
is a queue data structure, so it meets the first-in-first-out rule.
When the tasks in the main thread are empty after execution, the corresponding function will be read from Event Queue
and executed in the main thread.
The above process will be repeated continuously, which is often called Event Loop .
Let’s summarize it with a picture
Let me 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
We know that in js, synchronous tasks will be executed first before asynchronous tasks, so the above example will output log1、log2
first,
and then execute asynchronous tasks after the synchronous tasks are executed. Therefore, the callback function with a delay of 100
milliseconds will execute the output setTimeout 100
first. The
callback function with a delay of 1000
milliseconds will execute the output setTimeout 1000
later.
The above example is relatively simple. I believe that as long as you understand the synchronous and asynchronous tasks mentioned by the author above, there will be no problem. . Then let me give you another example. Friends, let’s see what the output will be.
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();
To solve the above problem, it is not enough to know synchronous and asynchronous tasks. We also need to know macro tasks and micro tasks.
In js
, tasks are divided into two types, one is called macro task MacroTask
, and the other is called micro task MicroTask
.
The common macro task MacroTask
has
the main code block
setTimeout()
setInterval()
setImmediate() - Node
requestAnimationFrame() - the browser.
The common micro task MicroTask
has
Promise.then()
process.nextTick() - Node.
So in the above example It involves macro tasks and micro tasks. What is the execution order of macro tasks and micro tasks?
First of all, when the overall script
(as the first macro task) starts to be executed, all the code will be divided into two parts: synchronous tasks and asynchronous tasks. The synchronous tasks will directly enter the main thread for execution in sequence, and the asynchronous tasks will enter the asynchronous queue and then Divided into macro tasks and micro tasks.
The macro task enters Event Table
and registers a callback function in it. Whenever the specified event is completed, Event Table
will move this Event Queue
to the Event Queue. The micro task will also enter another Event Table
and register in it. Callback function. Whenever the specified event is completed, Event Table
will move this function to Event Queue
When the tasks in the main thread are completed and the main thread is empty, Event Queue
of the microtask will be checked. If there are tasks, all Execute, if not, execute the next macro task.
We use a picture to summarize it.
After understanding the above examples of macro tasks and micro tasks in asynchronous, we can easily get the answer.
We know that in js, synchronous tasks will be executed first before asynchronous tasks, so the above example will output log1、new promise、log2
first. It should be noted here that the main code block of the new promise is synchronized.
After the macro task is executed, all micro tasks generated by this macro task will be executed, so promise.then
will be output.
After all micro tasks are executed, another macro task will be executed, delaying The callback function of 100
milliseconds will prioritize the execution and output setTimeout 100
This macrotask does not generate microtasks, so there are no microtasks that need to be executed
to continue executing the next macrotask. The callback function with a delay of 1000
will prioritize the execution and output setTimeout 1000
so after the test2 method is executed, it will Output log1、new promise、log2、promise.then、setTimeout 100、setTimeout 1000
in sequence
. Articles on the Internet have different opinions on whether to execute
js
first with macro tasks and then micro tasks or with micro tasks before macro tasks. The author's understanding is that if the entirejs
code block is regarded as a macro task, ourjs
execution order is macro task first and then micro task.
As the saying goes, practicing once is better than watching a hundred times. I will give you two examples below. If you can do it correctly, then you have mastered the knowledge of js
execution mechanism.
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();
Let’s analyze it in detail.
First, the overall js
code block is executed as a macro task, and 1, 1、6、12
are output in sequence.
After the overall code block macro task is executed, one micro task and two macro tasks are generated, so the macro task queue has two macro tasks and the micro task queue has one micro task.
After the macro task is executed, all micro tasks generated by this macro task will be executed. Because there is only one microtask, 7
will be output. This microtask spawned another macrotask, so there are currently three macrotasks in the macrotask queue.
Among the three macrotasks, the one with no delay set is executed first, so 8
is output. This macrotask does not generate microtasks, so there are no microtasks to execute, and the next macrotask continues to be executed.
Delay the execution of the macrotask for 100
milliseconds, output 9、10
, and generate a microtask, so the microtask queue currently has a microtask.
After the macrotask is executed, all microtasks generated by the macrotask will be executed, so the microtask will be executed. All microtasks in the task queue output 11
The macrotask execution outputs 2、3、5
with a delay of 1000
milliseconds, and a microtask is generated. Therefore, the microtask queue currently has a microtask.
After the macrotask is executed, the macrotask will be executed. All microtasks are generated, so all microtasks in the microtask queue will be executed, and 4
will be output
. So the above code example will output 1、6、12、7、8、9、10、11、2、3、5、4
, 12, 7, 8, 9, 10, 11, 2, 3, 5, 4 in sequence. Did you guys do it right?
Example 2:
We slightly modify the above example 1 and 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();
What will the above example output? Here we can easily solve the problem of async
and await
.
We know async
and await
are actually syntax sugar for Promise
. Here we only need to know await
is equivalent to Promise.then
. So we can understand the above example 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();
Can you easily get the result after seeing the above code?
First, the entire js
code block is initially executed as a macro task, and outputs 1、6、13
in sequence.
After the overall code block macro task is executed, two micro tasks and one macro task are generated, so the macro task queue has one macro task and the micro task queue has two micro tasks.
After the macro task is executed, all micro tasks generated by this macro task will be executed. So 7、Promise.resolve、12
will be output. This microtask spawned two more macrotasks, so the macrotask queue currently has three macrotasks.
Among the three macrotasks, the one with no delay set is executed first, so 8
is output. This macrotask does not generate microtasks, so there are no microtasks to execute, and the next macrotask continues to be executed.
Delay the execution of the macrotask for 100
milliseconds, output 9、10
, and generate a microtask, so the microtask queue currently has a microtask.
After the macrotask is executed, all microtasks generated by the macrotask will be executed, so the microtask will be executed. All microtasks in the task queue output 11
The macrotask execution outputs 2、3、5
with a delay of 1000
milliseconds, and a microtask is generated. Therefore, the microtask queue currently has a microtask.
After the macrotask is executed, the macrotask will be executed. All microtasks generated will execute all microtasks in the microtask queue and output 4
Therefore, the above code example will output 1, 6, 13, 7 1、6、13、7、Promise.resolve、12、8、9、10、11、2、3、5、4
4, did you guys do it right?
Many friends may still not understand setTimeout(fn)
. Isn’t it obvious that the delay time is not set? Shouldn’t it be executed immediately?
We can understand setTimeout(fn)
as setTimeout(fn,0)
, which actually means the same thing.
We know that js is divided into synchronous tasks and asynchronous tasks. setTimeout(fn)
is an asynchronous task, so even if you do not set a delay time here, it will enter the asynchronous queue and will not be executed until the main thread is idle.
The author will mention it again, do you think that the delay time we set after setTimeout
, js
will definitely be executed according to our delay time? I don’t think so. The time we set is only that the callback function can be executed, but whether the main thread is free is another matter. We can give a simple example.
function test5() { setTimeout(function () { console.log("setTimeout"); }, 100); let i = 0; while (true) { i++; } } test5();
Will the above example definitely output setTimeout
after 100
milliseconds? No, because our main thread has entered an infinite loop and has no time to execute asynchronous queue tasks.
GUI渲染
is mentioned here. Some friends may not understand it. I will introduce it in detail in an article about browsers later. Here is just a brief understanding.
Since JS引擎线程
and GUI渲染线程
are mutually exclusive, in order to enable宏任务
and DOM任务
to proceed in an orderly manner, the browser will start GUI渲染线程
after the execution result of one宏任务
and before the execution of the next宏任务
. , render the page.
Therefore, the relationship between macro tasks, micro tasks, and GUI rendering is as follows:
macro task -> micro task -> GUI rendering -> macro task ->...
[Related video tutorial recommendation: web front end]
The above is an in-depth analysis of JavaScript For details on the execution context and execution mechanism, please pay attention to other related articles on the PHP Chinese website for more information!