Give you a deep understanding of the js event loop mechanism

Synchronous tasks and asynchronous tasks (microtasks and macrotasks)

JavaScript is a single-threaded language

Divided into synchronous tasks and asynchronous tasks

A synchronous task refers to a task that is queued for execution on the main thread. Only when the previous task is completed can the next task be executed.

Asynchronous tasks refer to tasks that do not enter the main thread but enter the "task queue"; only after the main thread tasks are all executed, the tasks in the "task queue" will enter the main thread for execution.

Asynchronous tasks are divided into macro tasks and micro tasks

new promise(), console.log() are synchronous tasks

Macro task (macrotask) microtask
who initiated Host (Node, browser) JS engine
specific incident 1. script (can be understood as outer synchronization code) 2. setTimeout/setInterval 3. UI rendering/UI event 4. postMessage, MessageChannel 5. setImmediate, I/O (Node.js) 1. Promise 2. MutaionObserver 3. Object.observe (deprecated; Proxy object replacement) 4. process.nextTick (Node.js)
who runs first post run run first
Will a new round of Tick be triggered? meeting Won't

Execution process: synchronous task —> micro task —> macro task

1. Execute all synchronous tasks first, and put asynchronous tasks into the task queue
2. After the synchronous task is executed, start to execute all current asynchronous tasks
3. Execute all micro tasks in the task queue first
4. Then execute a macro task
5 . Then execute all microtasks
6. Execute a macrotask again, and then execute all microtasks... and so on until the end of execution.

This loop of 3-6 is called the event loop Event Loop

The event loop is a method for JavaScript to achieve asynchrony, and it is also the execution mechanism of JavaScript

async/await (emphasis)

(Personal note: the bottom layer of async/await is still Promise, so it is a microtask, but await is special)

async

When we use async before a function, the function returns a Promise object

async function test() {return 1 // async的函数会在这里帮我们隐士使用Promise.resolve(1)
}
// 等价于下面的代码
function test() { return new Promise(function(resolve, reject) { resolve(1) })
}
// 可见async只是一个语法糖,只是帮助我们返回一个Promise而已 

await

await means to wait, and it is the result of the "expression" on the right. The result of this expression can be the value of a Promise object or a function (in other words, there are no special restrictions). and can only be used inside with async

When await is used, it will be executed from right to left. When await is encountered, ★★★★★ will block the code behind it inside the function to execute the synchronization code outside the function. When the external synchronization code is executed, it will return Execute the rest of the code inside the function★★★★★, and when the await execution is completed, the code of the microtask queue will be processed first

example

//1
console.log('1');//2
setTimeout(function() {console.log('2');process.nextTick(function() {console.log('3');})new Promise(function(resolve) {console.log('4');resolve();}).then(function() {console.log('5')})
})
//3
process.nextTick(function() {console.log('6');
})
//4
new Promise(function(resolve) {console.log('7');resolve();
}).then(function() {console.log('8')
})
//5
setTimeout(function() {console.log('9');process.nextTick(function() {console.log('10');})new Promise(function(resolve) {console.log('11');resolve();}).then(function() {console.log('12')})
})

// 先执行1 输出1
// 执行到2,把setTimeout放入异步的任务队列中(宏任务)
// 执行到3,把process.nextTick放入异步任务队列中(微任务)
// 执行到4,上面提到promise里面是同步任务,所以输出7,再将then放入异步任务队列中(微任务)
// 执行到5,同2
// 上面的同步任务全部完成,开始进行异步任务
// 先执行微任务,发现里面有两个微任务,分别是3,4压入的,所以输出6 8
// 再执行一个宏任务,也就是第一个setTimeout
// 先输出2,把process.nextTick放入微任务中,再如上promise先输出4,再将then放入微任务中
// 再执行所以微任务输出输出3 5
// 同样的,再执行一个宏任务setTImeout2,输出9 11 在执行微任务输出10 12
// 所以最好的顺序为:1 7 6 8 2 4 3 5 9 11 10 12 
async function async1() {console.log( 'async1 start' )await async2()console.log( 'async1 end' )
}

async function async2() {console.log( 'async2' )
}
console.log( 'script start' )
setTimeout( function () {console.log( 'setTimeout' )
}, 0 )
async1();
new Promise( function ( resolve ) {console.log( 'promise1' )resolve();
} ).then( function () {console.log( 'promise2' )
} )
console.log( 'script end' )

// 首先执行同步代码,console.log( 'script start' )
// 遇到setTimeout,会被推入宏任务队列
// 执行async1(), 它也是同步的,只是返回值是Promise,在内部首先执行console.log( 'async1 start' )
// 然后执行async2(), 然后会打印console.log( 'async2' )
// 从右到左会执行, 当遇到await的时候,阻塞后面的代码,去外部执行同步代码
// 进入new Promise,打印console.log( 'promise1' )
// 将.then放入事件循环的微任务队列
// 继续执行,打印console.log( 'script end' )
// 外部同步代码执行完毕,接着回到async1()内部, 继续执行 await async2() 后面的代码,执行 console.log( 'async1 end' ) ,所以打印出 async1 end 。(个人理解:async/await本质上也是Promise,也是属于微任务的,所以当遇到await的时候,await后面的代码被阻塞了,应该也是被放到微任务队列了,当同步代码执行完毕之后,然后去执行微任务队列的代码,执行微任务队列的代码的时候,也是按照被压入微任务队列的顺序执行的)
// 执行微任务队列的代码, 打印 console.log( 'promise2' )
// 进入第二次事件循环,执行宏任务队列, 打印console.log( 'setTimeout' )
/**
 * 执行结果为:
 * script start
 * async1 start
 * async2
 * promise1
 * script end
 * async1 end
 * promise2
 * setTimeout
 */ 
console.log(1);
async function fn(){console.log(2)new Promise((resolve)=>{resolve();}).then(()=>{console.log("XXX")})await console.log(3)console.log(4)
}
fn();
new Promise((resolve)=>{console.log(6)resolve();
}).then(()=>{console.log(7)
})
console.log(8)

// 执行结果为:1 2 3 6 8 XXX 4 7
/*
前面的 1 2 3 6 8 不再解析,重点是后面的 XXX 4 7,由此可见 await console.log(3) 之后的代码 console.log(4) 是被放入到微任务队列了,
代码 console.log("XXX") 也是被压入微任务队列了,console.log("XXX")是在 console.log(4) 之前,
所以当同步任务执行完毕之后,执行微任务队列代码的时候,优先打印出来的是 XXX ,然后才是 4 。
*/ 
console.log(1);
async function fn(){console.log(2)await console.log(3)await console.log(4)await console.log("await之后的:",11)await console.log("await之后的:",22)await console.log("await之后的:",33)await console.log("await之后的:",44)
}
setTimeout(()=>{console.log(5)
},0)
fn();
new Promise((resolve)=>{console.log(6)resolve();
}).then(()=>{console.log(7)
})
console.log(8)

/**
 * 执行结果为:
 * 1
 * 2
 * 3
 * 6
 * 8
 * 4
 * 7
 * await之后的: 11
 * await之后的: 22
 * await之后的: 33
 * await之后的: 44
 * 5
 */
/*
由此可见,代码执行的时候,只要碰见 await ,都会执行完当前的 await 之后,
把 await 后面的代码放到微任务队列里面。但是定时器里面的 5 是最后打印出来的,
可见当不断碰见 await ,把 await 之后的代码不断的放到微任务队列里面的时候,
代码执行顺序是会把微任务队列执行完毕,才会去执行宏任务队列里面的代码。
*/ 
Promise.resolve().then(() => {console.log(0);return Promise.resolve(4) // 顺延2位如果是return 4 则打印 0、1、4、2、3、5、6、7
}).then(res => console.log(res))
 
Promise.resolve().then(() => {console.log(1);
}).then(() => {console.log(2);
}).then(() => {console.log(3);
}).then(() => {console.log(5);
}).then(() => {console.log(6);
}).then(() => {console.log(7);
})
/*
此题主要注意的是原生的Promise的then方法中,如果返回的是一个普通值,则返回的值会被立即调用并赋值给resolve函数,
如果返回的是一个thenable,则then方法将会被放入到微队列中执行,
如果返回的是一个Promise.resolve,则会再加一次微任务队列。
即微任务后移,Promise.resolve本身是执行then方法,而then方法本身是在微任务队列中执行,
同时return Promise.resolve时是将resolve调用的返回值 作为上级then中resolve的参数传递,
调用外层then方法时本身是在微队列里面,所以函数的执行顺序是要在微队列中下移两次。

*/ 

According to the latest explanation from w3c

  • Each task has a task type. Tasks of the same type must be in one queue, that is, there are multiple queues. Different types of tasks can belong to different queues. In a sub-event cycle, the browser can Differentiate task execution from different queues
  • The browser must prepare a micro-queue. The tasks in the micro-queue have priority and all other tasks execute the things in it. All have to wait for me. Even the drawing task has to wait, which is the highest priority.

W3C no longer uses macro queues as browser complexity skyrockets

At least the following queues are included in the current chrome implementation

  • Delay queue: used to store callback tasks after the timer arrives, with priority
  • Interactive queue: used to store event processing tasks generated after user operations, with high priority
  • Micro-queue: The user stores the tasks that need the fastest execution with the highest priority

The main way to add tasks to the microqueue is to use Promise and MutationObserver

例如:
// 立即把一个函数添加到微队列
Promise.resolve().then(函数) 

Do tasks have priorities?

  • Tasks have no priority, first in first out in the message queue
  • But the message queue has priority
// 立刻把一个函数添加到微队列 最高执行
promise.resolve().then(函数)

setTimeOut(()=>{ // 第三步执行延时队列中的任务console.log(1);
},0)

promise.resolve().then(()=>{ // 第二步执行微队列中的任务console.log(2);
})

console.log(3); // 第一步先执行全局js

// 3 2 1 

interview questions

1. How to understand the asynchrony of JS?

JS is a single-threaded language because it runs on the browser's main rendering thread, and there is only one rendering main thread.

The rendering main thread undertakes many tasks, rendering pages and executing JS are all running in it.

If you use a synchronous method, it is very likely that the main thread will be blocked, which will cause many other tasks in the message queue to be unable to be executed. In this way, on the one hand, the busy main thread will waste time in vain, and on the other hand, the page cannot be updated in time, causing the user to be stuck.

So the browser uses an asynchronous way to avoid it. The specific method is that when certain tasks occur, such as timers, networks, and event monitoring, the main thread will hand over the tasks to other threads for processing, and immediately end the execution of the tasks by itself, and then execute subsequent codes. When other threads are finished, wrap the callback function passed in advance into a task, add it to the end of the message queue, and wait for the main thread to schedule execution.

In this asynchronous mode, the browser never blocks, thus ensuring the smooth operation of the single thread to the greatest extent.

2. Explain the event loop of js

The event loop, also known as the message loop, is the way the browser renders the main thread.

In the source code of Chrome, it starts an endless for loop, each loop fetches the first task from the message queue for execution, and other threads only need to add the task to the end of the queue at an appropriate time.

In the past, message queues were simply divided into macro queues and micro queues. This statement can no longer satisfy the complex browser environment, and a more flexible and changeable processing method has been replaced.

According to the official W3C explanation, each task has a different type, tasks of the same type must be in the same queue, and different tasks can belong to different queues. Different task queues have different priorities. In an event loop, the browser decides which queue to take. But the browser must have a micro-queue, and the tasks of the micro-queue must have the highest priority, and must be scheduled and executed first.

3. Can the timer in JS achieve accurate timing? Why?

No, because:

1. The computer hardware does not have an atomic clock, so accurate timing cannot be achieved.
2. The timing function of the operating system itself has a small amount of deviation. Since the JS timer finally calls the function of the operating system, it also carries these deviations.
3. According to W3C Standard, when the browser implements the timer, if the nesting level exceeds 5 layers, it will have a minimum time of 4 milliseconds, which will bring a deviation when the timing time is less than 4 milliseconds. 4. Affected by the event loop,
timing The callback function of the controller can only run when the main thread is idle, thus introducing a bias

at last

Organized a set of "Interview Collection of Front-end Manufacturers", including HTML, CSS, JavaScript, HTTP, TCP protocol, browser, VUE, React, data structure and algorithm, a total of 201 interview questions, and made an answer for each question Answer and analyze.

Friends in need, you can click the card at the end of the article to receive this document and share it for free

Part of the documentation shows:



The length of the article is limited, and the following content will not be displayed one by one

Friends in need, you can click the card below to get it for free

Guess you like

Origin blog.csdn.net/Android_boom/article/details/127678182