夜有长歌
长歌一曲烟霭深

首先什么也不要去想,直接看代码并思考一个问题:它打印的顺序是什么?

 1 console.log('script start');
 2 
 3 setTimeout(function () {
 4   console.log('setTimeout');
 5 }, 0);
 6 
 7 Promise.resolve()
 8   .then(function () {
 9     console.log('promise1');
10   })
11   .then(function () {
12     console.log('promise2');
13   });
14 
15 console.log('script end');

看答案:

 

 

对错都不要着急,先了解下基本概念:

js是单线程的:单线程 : 同一时间下只能做一件事避免DOM渲染冲突

  1. 浏览器需要渲染DOM
  2. JS可以修改DOM结构
  3. JS执行的时候,浏览器DOM渲染会暂停
  4. 两端JS不能同时执行(都修改DOM就冲突了)
  5. webworker能实现多线程,但是不能访问DOM 

宏任务(task)

浏览器为了能够使得JS内部 task 与DOM任务能够有序的执行,会在一个 task 执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
比如:鼠标点击会触发一个事件回调,需要执行一个 task ,然后解析HTMl。

在上面的例子中:

setTimeout的作用是等待给定的时间后为它的回调产生一个新的宏任务。这就是为什么打印‘setTimeout’在‘script end’之后。因为打印‘script end’是第一个宏任务里面的事情,而‘setTimeout’是另一个独立的任务里面打印的。

微任务(Microtasks )

微任务通常来说就是需要在当前 task 执行结束后立即执行的任务。

比如:对一系列动作做出反馈,或者是需要异步的执行任务而又不需要分配一个新的 task,这样可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。一旦一个pormise有了结果,或者早已有了结果(有了结果是指这个promise到了fulfilled或rejected状态),他就会为它的回调产生一个微任务,这就保证了回调异步的执行即使这个promise早已有了结果。所以对一个已经有了结果的promise调用.then()会立即产生一个微任务。这就是为什么‘promise1’,'promise2'会打印在‘script end’之后,因为所有微任务执行的时候,当前执行栈的代码(宏任务)必须已经执行完毕。‘promise1’,'promise2'会打印在‘setTimeout’之前是因为所有微任务总会在下一个宏任务之前全部执行完毕。

偷张图:😋

基于上面的例子来看下:

Event Queue:事件队列

  1. 首先浏览器执行js进入第一个宏任务进入主线程, 遇到 setTimeout  分发到第二个宏任务Event Queue中;
  2. 遇到 console.log() 直接执行 输出 script start;
  3. 执行 then 被分发到微任务Event Queue中;
  4. 遇到 console.log() 直接执行 输出 script end
  5. 第一个宏任务执行结束,开始执行第一个微任务打印 'promise1'promise2'
  6. 第一轮微任务执行完毕,执行第二个宏事件,打印 setTimeout 里面内容'setTimeout'

 

直接列出常见的宏任务与微任务:(不做标注代表web和node环境都支持)

宏任务:setTimeout  setInterval  setImmediate(web环境不支持)  requestAnimationFrame(node环境不支持);

微任务:process.nextTick(web不支持) MutationObserver(node不支持) Promise.then catch finally

 

 

摘:https://www.cnblogs.com/ckAng/p/11133643.html

 1 //主线程直接执行
 2 console.log('1');
 3 //丢到宏事件队列中
 4 setTimeout(function() {
 5     console.log('2');
 6     process.nextTick(function() {
 7         console.log('3');
 8     })
 9     new Promise(function(resolve) {
10         console.log('4');
11         resolve();
12     }).then(function() {
13         console.log('5')
14     })
15 })
16 //微事件1
17 process.nextTick(function() {
18     console.log('6');
19 })
20 //主线程直接执行
21 new Promise(function(resolve) {
22     console.log('7');
23     resolve();
24 }).then(function() {
25     //微事件2
26     console.log('8')
27 })
28 //丢到宏事件队列中
29 setTimeout(function() {
30     console.log('9');
31     process.nextTick(function() {
32         console.log('10');
33     })
34     new Promise(function(resolve) {
35         console.log('11');
36         resolve();
37     }).then(function() {
38         console.log('12')
39     })
40 })
  • 首先浏览器执行js进入第一个宏任务进入主线程, 直接打印console.log('1')
  • 遇到 setTimeout  分发到宏任务Event Queue中
  • 遇到 process.nextTick 丢到微任务Event Queue中
  • 遇到 Promise, new Promise 直接执行 输出 console.log('7');
  • 执行then 被分发到微任务Event Queue中
  • 第一轮宏任务执行结束,开始执行微任务 打印 6,8
  • 第一轮微任务执行完毕,执行第二轮宏事件,执行setTimeout
  • 先执行主线程宏任务,在执行微任务,打印'2,4,3,5'
  • 在执行第二个setTimeout,同理打印 ‘9,11,10,12’
  • 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。

以上是在浏览器环境下执行的数据,只作为宏任务和微任务的分析,我在node环境下测试打印出来的顺序为:1,7,6,8,2,4,9,11,3,10,5,12。

node环境执行结果和浏览器执行结果不一致的原因是:浏览器的Event loop是在HTML5中定义的规范,而node中则由libuv库实现。libuv库流程大体分为6个阶段:timers,I/O callbacks,idle、prepare,poll,check,close callbacks,和浏览器的microtask,macrotask那一套有区别。

 

end...

posted on 2021-03-02 17:51  夜有长歌  阅读(152)  评论(0)    收藏  举报