直接从两个题目开始吧:

console.log('start');
setTimeout(function() {
  console.log('timeout1');
}, 0);
new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 0);
}).then(function() {
    console.log('then1');
    setTimeout(() => console.log('timeout3'), 0);
}).then(function() {
    console.log('then2');
    setTimeout(() => console.log('timeout4'), 0);
})
console.log('end');
console.log('start');
setTimeout(function() {
  console.log('timeout1');
}, 30);
new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 20);
}).then(function() {
    console.log('then1');
    setTimeout(() => console.log('timeout3'), 10);
})
console.log('end');

请写出这两段代码的输出顺序。

如果你可以正确写出答案,并清楚的知道整个Event执行流程的话,就可以跳过该文章了。

这里给出答案:

题1:start promise1 end then1 then2 timeout1 timeout2 timeout3 timeout4
题2:start promise1 end then1 timeout3 timeout2 timeout1

1. 背景

JavaSript是一门单线程语言,所以为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,用户引擎必须使用event loops。
Event Loop包含两类:一类是基于Browsing Context,一种是基于Worker,二者是独立运行的。本文讲述的是基于Browsing Context的事件循环机制。

2. 相关概念

在JavaScript中,任务可以分为同步任务异步任务
同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout定时函数等都属于异步任务,异步任务会通过任务队列(Event Queue)的机制来进行协调。

  • Execution Stack(主执行栈)
  • Event Queue(任务队列)

    • Macro Task(宏任务)

      • setTimeout
      • setInterval
      • I/O、UI交互事件
      • setImmediate(Node.js 环境)
    • Micro Task(微任务)

      • Promise
      • MutaionObserver
      • process.nextTick(Node.js 环境)
注:其实这里的表述不严谨,具体见文末

执行过程:同步和异步任务分别进入不同的执行环境

  • 同步任务进入主线程,即主执行栈;
  • 异步任务进入Event Queue;
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的任务,推入主线程执行;
  • 在Event Queue中,需要把所有微任务执行完后再执行宏任务;

上述过程的不断重复就是我们说的Event Loop(事件循环)。

3. 题目解析

在了解了相关概念和执行过程后,我们拿 题目1 具体分析一下。
为了对照方便,我们把题目拷过来:

console.log('start');
setTimeout(function() {
  console.log('timeout1');
}, 0);
new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 0);
}).then(function() {
    console.log('then1');
    setTimeout(() => console.log('timeout3'), 0);
}).then(function() {
    console.log('then2');
    setTimeout(() => console.log('timeout4'), 0);
})
console.log('end');

逐行分析:

  1. console.log('start') : 同步任务,进入主执行栈
  2. console.log('timeout1') : 异步任务,并作为宏任务进入Event Queue
  3. console.log('promise1') : 同步任务,进入主执行栈
  4. console.log('timeout2') : 异步任务,并作为宏任务进入Event Queue
  5. console.log('then1') : 异步任务,并作为微任务进入Event Queue
  6. console.log('timeout3') : 异步任务,并作为宏任务进入Event Quene(该setTimeout虽然在微任务中)
  7. console.log('then2') : 异步任务,并作为微任务进入Event Queue
  8. console.log('timeout4') : 异步任务,并作为宏任务进入Event Quene(该setTimeout虽然在微任务中)
  9. console.log('end') : 同步任务,进入主执行栈

执行顺序:

  1. 主执行栈先执行:start promise1 end
  2. Event Queue微任务执行:then1 then2
  3. Event Queue微任务执行:timeout1 timeout2 timeout3 timeout4

時雨
91 声望6 粉丝

慢慢地把云笔记上的内容搬过来~