It is well known that JavaScript is run on a single thread and can be executed asynchronously. In general, such single-threaded and asynchronous languages are event-driven, and browsers provide such an environment for JavaScript


      
  1. setTimeout(function(argument) {

  2.  console.log('- 1 - - -)

  3. }, 0)

  4. console.time("test")

  5. for (var i = 0; i < 1000000; i+ +) {

  6.  i = = = (100000 - 1)

  7. }

  8. console.timeEnd("test")

  9. console.log('- 2 -)

Copy the code

The output on my computer is:

Test: 5.4892578125 ms

2 — — — — — —

1 — — — — — —

Oh, it doesn’t make sense ah, clearly I set 0 milliseconds after the print ‘–1–‘ situation, open the front bible look, there is a sentence:

This is because even though setTimeout was called with a delay of zero, it’s placed on a queue and scheduled to run at the next opportunity; not immediately. Currently-executing code must complete before functions on the queue are executed, thus the resulting execution order may not be as expected.

After the waiting time of setTimeout ends, it is not executed directly, but pushed into a task queue of the browser first. After the synchronization queue ends, tasks in the task queue are called successively.

This involves several threads in the browser, and typically browsers have the following threads

  1. Js engine threads (interpreting and executing JS code, user input, network requests)

  2. GUI thread (draws the user interface and is mutually exclusive with the JS main thread)

  3. HTTP network request thread (processing users’ GET, POST and other requests, etc., and pushing the callback function to the task queue after the result is returned)

  4. Timing trigger thread (setTimeout, setInterval after waiting time to push the execution function into the task queue)

  5. Browser event processing thread (putting click, Mouse, and other interactive events into an event queue after they occur)

Execution stacks and callbacks for JavaScript functions


      
  1. function test1(a) {

  2.  test2(a)

  3.  console.log('Hi, I'm test1')

  4. }

  5. function test2(a) {

  6.  console.log('Hi, I'm Test2')

  7. }

  8. function main(a) {

  9.  console.log('Hello, I'm Main')

  10.  setTimeout(() = > {

  11.    console.log('Hi, I'm setTimeout.')

  12.  }, 0)

  13.  test1(a)

  14. }

  15. main(a)

Copy the code

The result is as follows:

Hello, I’m Main

Hello, I’m Test2

Hello, I’m Test1

Hello, I’m setTimeout

When we call a function, its address, arguments, and local variables are all pushed into a stack

Step1: main() is called first, goes to the stack and prints’ hello, I’m main ‘

Step2: when setTimeout is encountered, put the callback function into the task queue

Step3: Main calls test1. Test1 enters the stack and is executed

Step4: Execute test1, and test1 invokes test2

Step5: test2 Execute, print ‘Hello, I am test2’

Step6: after test2 is executed, pop back to test1 from the stack and print ‘hello, I’m test1’.

Step6: after the execution of the main thread, enter the callback queue and execute the callback function of setTimeout. Print “hello everyone, I am setTimeout”. By now, the whole program is finished, but the event loop is waiting for other callback functions.

It looks something like this in code


      
  1. while (queue.waitForMessage()) {

  2.  queue.processNextMessage(a)

  3. }

Copy the code

The thread is always waiting for other callbacks such as click, setTimeout, etc

Use a graph to represent it as follows:

Macrotask and microtask

So let’s look at a little bit of code and think about how this works out, right


      
  1.  setTimeout1 = setTimeout(function(a) {

  2.    console.log('- 1 - - -)

  3.  }, 0)

  4.  setTimeout2 = setTimeout(function(a) {

  5.    Promise.resolve(a)

  6.      .then(() = > {

  7.        console.log('- 2 -)

  8.      })

  9.    console.log('- 3 -)

  10.  }, 0)

  11.  new Promise(function(resolve) {

  12.    console.time("Promise")

  13.    for (var i = 0; i < 1000000; i+ +) {

  14.      i = = = (1000000 - 1) && resolve(a)

  15.    }

  16.    console.timeEnd("Promise")

  17.  }).then(function(a) {

  18.    console.log('- 4 -)

  19.  });

  20.  console.log('- 5 -)

Copy the code

According to the above analysis, the browser will put the asynchronous callback function into a task queue. According to this analysis, when the program runs, the setTimeout function will be encountered first, and the setTimeout callback function will be put into the task queue. Further down, the setTimeout function will be encountered again. The setTimeout callback has a Promise object in it, but let’s wait until it’s in the task queue to execute, and then the browser interpreter will encounter an operation on the New Promise object, which is not executed asynchronously, A program run timer will start, and the time it takes to increment from 0 to 1000000 will be printed, and at I = 999999, the Promise state will change to resolve and the callback that resolve executes will be pushed into the task queue, and at the end of the program, The output is’ –5– ‘.

According to the above analysis, our program output order is:

The time required for the program to increment from 0 to 1000000

5 — — — — — —

1 — — — — — —

– 3 –

— — — — – 4

2 — — — — — —

Use a graph to represent it as follows:

Run it in the browser to see the result:

Promise: 5.151123046875 ms

5 — — — — — —

— — — — – 4

1 — — — — — —

– 3 –

2 — — — — — —

why

Because browsers have more than one task queue, there are also microtasks and MacroTasks

microtasks:

  • process.nextTick

  • promise

  • Object.observe

  • MutationObserver

macrotasks:

  • setTimeout

  • setInterval

  • setImmediate

  • I/O

  • The UI rendering

According to the WHATWG specification:

  • An event loop can have one or more task queues.

  • Each event loop has a MicroTask Queue

  • task queue == macrotask queue ! = microtask queue

  • A task can be placed in a MacroTask Queue or a MicroTask Queue

  • The call stack is cleared (global only) and all microtasks are executed. When all executable microtasks have been executed. The loop starts again with macroTask, finds one task queue to complete, and then executes all microTasks, and so on

So, the correct execution steps should be:

Step1: execute the script and press the script to task queue


      
  1.  stacks: []

  2.  task queue: [script]

  3.  microtask queue: []

Copy the code

Step2: When setTimeout1 is encountered, setTimeout can be regarded as a task


      
  1.  stacks: [script]

  2.  task queue: [setTimeout1]

  3.  microtask queue: []

Copy the code

Step3: Proceed further and press setTimeout2 into the Task queue when it encounters setTimeout2


      
  1.  stacks: [script]

  2.  task queue: [setTimeout1.setTimeout2]

  3.  microtask queue: []

Copy the code

Step4: Continue the execution, new Promise, go down according to the synchronization process, output the execution time in new Promise


      
  1.  stacks: [script]

  2.  task queue: [setTimeout1.setTimeout2]

  3.  microtask queue: []

Copy the code

Step5: Resolve () occurs when I = 99999, and the successful callback is added to the MicroTask queue


      
  1.  stacks: [script]

  2.  task queue: [setTimeout1.setTimeout2]

  3.  microtask queue: [console.log('- 4 -)]

Copy the code

Log (‘– 5– ‘), and the task in the stacks queue is finished


      
  1.  stacks: []

  2.  task queue: [setTimeout1.setTimeout2]

  3.  microtask queue: [console.log('- 4 -)]

Copy the code

If stacks are empty, the event polling thread will push the task in the microTask queue to the stacks and then print”


      
  1.  stacks: []

  2.  task queue: [setTimeout1.setTimeout2]

  3.  microtask queue: []

Copy the code

If the microTask queue has an empty stack and the microtask queue has an empty stack, the task will be executed. If the microtask queue has an empty stack, if the microtask queue has an empty stack, the task will be executed. If the microtask queue has an empty stack, the task will be executed.


      
  1.  stacks: [setTimeout1.setTimeout2]

  2.  task queue: []

  3.  microtask queue: []

Copy the code

Step8: setTimeout2 then encounters a promise.resolve (), presses the successful callback to the microTask queue, and prints’ –3– ‘.


      
  1.  stacks: [setTimeout2]

  2.  task queue: []

  3.  microtask queue: [Promise.resolve()]

Copy the code

Step9: Execute microTask queue after setTimeout2 is finished, print ‘–2–‘, and the code section is finished


      
  1.  stacks: []

  2.  task queue: []

  3.  microtask queue: []

Copy the code

In a simple sentence: the entire JS code MacroTask executes first, the synchronous code executes after the microTask executes the microTask, no MicroTask executes the next MacroTask, and so on to the end of the loop

— — — — — — — — —

Long press the QR code to follow the big Zhuan FE