Is JS single thread or multi-thread

JS actually does not have the concept of thread, the so-called JS is a single thread is actually relative to multithreading, so JS does not have the ability to handle parallel tasks.

EventLoop in Different Environments

2.1 Macro and micro tasks

  • Microtasks are mandated by ES6 syntax
  • Macro tasks are specified by the browser

2.2 Nodejs and Browser

  • Nodejs events are based on Libuv, while browser event loops are clearly defined in the HTML5 specification.
  • Libuv has made an implementation of event Loop, while the HTML5 specification only defines the model of event loop in the browser, and the specific implementation is left to the browser manufacturer.

Therefore, asynchronous callback execution results are different in different browser environments. Here we will focus on Nodejs and EventLoop implementation principle in Chrome browser environment

EventLoop in Chrome

3.1 the sample a

async function async1(){
  console.log('async1 start')
  await async2();   
  console.log('async1 end')}new Promise((resolve,reject) = >{
  console.log('promise1')
  resolve()
}).then(() = >{ 
  console.log('promise2')})async function async2(){
  console.log('async2')
}
async1()
console.log('script start')
setTimeout(() = >{
  console.log('setTimeout')},0)
console.log('script end')
Copy the code
  • Run the synchronization code promise1 async1 start Async2 script start script end
  • During the execution of synchronous code we need to save the async1 context and exit aync1. Note that we are not pushing the code after await into the microtask queue !!!!
  • Push promise. then to the microtask queue and setTimeout to the macro task queue
  • Perform a microtask to export promise2
  • Async1 returns async1 end
  • Execute macro task setTimeout

Note that in newer browsers the code behind await is pushed into the microtask queue!

3.2 example 2

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1.0, user - scalable = 0">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="" />
  </head>
  <style type="text/css">
    .outer{
      width: 200px;
      height:200px;
      background-color: blue;
    }
    .inner{
      width:100px;
      height:100px;
      background-color: green;
      margin:0 auto;
    }
  </style>
  <body>
    <div class="outer">
      <div class="inner"></div>
    </div>
  </body>
  <script>
    var outer = document.querySelector('.outer');
    var inner = document.querySelector('.inner');
    new MutationObserver(function () {
      console.log('mutate');
    }).observe(outer, {
      attributes: true});function onClick() {
      console.log('click');
      setTimeout(function () {
        console.log('timeout');
      }, 0);
      Promise.resolve().then(function () {
        console.log('promise');
      });
      outer.setAttribute('data-random'.Math.random());
    }

    inner.addEventListener('click', onClick);
    outer.addEventListener('click', onClick);
  </script>
</html>
Copy the code

Analysis 1: When we only trigger outer’s click event

  • Sync code click
  • Microtask queue: promise.then () new MutationObserver()
  • Macro task queue :setTimeout
  • Output: click – > promise – > mutate – > a timeout

Analysis 2: When we trigger the inner click event (note the bubbling event!!)

  • Sync code click
  • Microtask queue: promise.then () new MutationObserver()
  • Macro task queue :setTimeout
  • Output: click – > promise – > mutate
  • Event bubbling (outer)
  • Sync code click
  • Microtask queue: promise.then () new MutationObserver()
  • Macro task queue :setTimeout
  • Output: click – > promise – > mutate
  • Output the macro task timeout->timeout

3.3 DOM rendering timing

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1.0, user - scalable = 0">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="" />
  </head>
  <style type="text/css"></style>
  <body>
    <div id="container"></div>
  </body>
  <script>
    const container = document.getElementById('container')
    const p = "

DOM render timing

"
container.innerHTML = p alert(container.children.length)
</script> </html> Copy the code

The JS execution stack has an alert function, so the DOM will not render. We render the DOM after the alert function completes. DOM rendering is done every time the JS stack is empty, and DOM rendering is done if needed

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1.0, user - scalable = 0">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="" />
  </head>
  <style type="text/css"></style>
  <body>
    <div id="container"></div>
  </body>
  <script>
    const container = document.getElementById('container')
    const p = "

DOM render timing

"
container.innerHTML = p setTimeout(() = >{ alert(` macro task${container.children.length}`)},0) Promise.resolve().then(() = >{ alert(Micro task `${container.children.length}`)})
</script> </html> Copy the code

DOM rendering is done after microtask execution and before macro task execution

3.4 the principle

All methods in JS will be pushed to the stack for execution, and the completion of execution will be ejected. When asynchronous code is encountered, for example, setTimeout MutationObserver Promise that the asynchronous part will be executed by other places in charge of webAPI. After the asynchronous result is obtained, The callback goes to the corresponding queue, the Promise MutationObserver callback goes to the micro-task queue, and setTimeout setInterval requestAnimationFrame callback goes to the macro task queue. When the execution stack of the waiting main thread is empty, the microtask queue is immediately pushed to the stack for execution, and the execution of the macro task queue begins

Note: The microtask is executed when the execution stack of the main thread is empty. If the main thread is not empty, the microtask is not executed

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1.0, user - scalable = 0">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="" />
  </head>
  <style type="text/css">
    .outer{
      width: 200px;
      height:200px;
      background-color: blue;
    }
    .inner{
      width:100px;
      height:100px;
      background-color: green;
      margin:0 auto;
    }
  </style>
  <body>
    <div class="outer">
      <div class="inner"></div>
    </div>
  </body>
  <script>
    var outer = document.querySelector('.outer');
    var inner = document.querySelector('.inner');
    new MutationObserver(function () {
      console.log('mutate');
    }).observe(outer, {
      attributes: true});function onClick() {
      console.log('click');
      setTimeout(function () {
        console.log('timeout');
      }, 0);

      Promise.resolve().then(function () {
        console.log('promise');
      });
      outer.setAttribute('data-random'.Math.random());
    }
    inner.addEventListener('click', onClick);
    outer.addEventListener('click', onClick);
    inner.click();
  </script>
</html>
Copy the code
  • In case 2, we need to manually click on the internal element to trigger the click event and push the click event into the JS execution stack, which will be pushed out again when the execution is finished. At this point, before bubbling, the execution stack is empty so that the microtask queue can be executed.
  • However, when we use the script to call, the script will be pushed out of the JS execution stack only after the click events of the inner and outer elements have been processed, so we need to wait until the end to execute the microtask queue.

EventLoop under Nodejs

4.1 the difference between

  • The JS stack in the browser can only perform one task, that is, your microtask or macro task can only be pushed when the JS stack is empty.
  • NodeJS allows you to perform multiple macro tasks simultaneously. (This feature was fixed to be browser-compatible after 11.x.)
  • NodeJS defines multiple macro task categories and their corresponding priorities
  • Process.nexttick () takes precedence over all microtasks, and each time the list of microtasks is cleared, process.nexttick () is executed first.

4.2 Six Phases (see NodeJS website)

  • Timers: Execute the callback that expires in setTimeout() and setInterval().
  • I/O Callbacks: A small number of I/ OCallbacks in the previous cycle are delayed until this stage of the cycle
  • Idle, prepare: Used internally only
  • Poll: The most important phase, performing I/O callback, blocks in this phase if appropriate (file read, HTTP request)
  • Check: Performs the Callback of setImmediate
  • Close callbacks: Callback to execute a close event, such as socket.on(“close”,func)

4.3 case

const fs = require('fs');

setImmediate(() = > {
  console.log('setImmediate');
});

fs.readdir(__dirname, () = > {
  console.log('fs.readdir');
});

setTimeout(() = >{
  console.log('setTimeout');
});

Promise.resolve().then(() = > {
  console.log('promise');
});
Copy the code
const fs = require('fs');
fs.readdir(__dirname, () = > {
  console.log('fs.readdir');
  setImmediate(() = > {
    console.log('setImmediate');
  });
  setTimeout(() = >{
    console.log('setTimeout');
  });
});
Copy the code
  • While setTimeout() and setImmediate() are both written inside main, it doesn’t necessarily matter who does it first and who doesn’t
  • SetImmediate () does not mediate when setTimeout() and setImmediate() are both written inside an I/O callback or the callback of a poll macro task, setImmediate() must be executed first SetTimeout (), because the fifth phase is after the second or fourth phase.

Node does not guarantee that timers will be executed immediately after the preset time because Node's expiration check on timers is not reliable and may be affected by other programs running on the machine, or the main thread is not idle at that time point although setTimeout is 0. However, Node generally sets 0 to 1ms. Therefore, when the time for Node to prepare event loop is longer than 1ms, setTimeout will be executed first when it enters the timers stage and setTimeout has expired. Instead, if the timers phase is less than 1ms and setTimeout has not expired, the state will miss the timers phase and enter the check phase before executing setImmediate

setImmediate(() = > {
  console.log('timeout1')
  Promise.resolve().then(() = > console.log('promise resolve'))
  process.nextTick(() = > console.log('next tick1'))}); setImmediate(() = > {
  console.log('timeout2')
  process.nextTick(() = > console.log('next tick2'))}); setImmediate(() = > console.log('timeout3'));
setImmediate(() = > console.log('timeout4'));
process.nextTick(() = >console.log('tick'))
Promise.resolve().then(() = >{console.log('promise')})
Copy the code

Process.nexttick () takes precedence over all microtasks