一、JavaScript 是一门单线程语言,是按照语句出现的顺序执行。
二、JavaScript 事件循环分为:同步任务、异步任务。
- 任务进入执行栈之后,同步任务和异步任务分别进入不同的执行场所,同步的进入主线程,异步的进入Event Table并注册函数。
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空之后,会去Event Queue中读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop事件循环。
let data = [];
$.ajax({
    url: www.javascript.com,
    data: data,
    success: () => {
      console.log('发送成功!');
    }
})
console.log('代码执行结束');
上面是一段简易的 Ajax 请求代码:
1、Ajax 进入 Event Table,注册回调函数 success。
2、执行 console.log(‘代码执行结束’)。
3、Ajax 事件完成,回调函数 success 进入 Event Queue。
4、主线程从 Event Queue 读取回调函数 success 并执行。
三、setTimeout 异步延时执行
setTimeout(() => {
  task();
}, 3000)
console.log('执行console');
// 先执行 console.log 这个同步任务,再执行 task()
setTimeout(() => {
  task()
}, 3000)
sleep(10000000)
// 控制台执行 task() 需要的时间远远超过 3 秒
上面代码执行过程:
1、task()进入 Event Table 并注册,计时开始。
2、执行sleep函数,很慢,非常慢,计时仍在继续。
3、3 秒到了,计时事件setTimeout完成,task()进入 Event Queue,但是sleep还没执行完,只好等着。
4、sleep终于执行完了,task()终于从 Event Queue 进入了主线程执行。
总结:setTimeout这个函数,是经过指定时间后,把要执行的任务task()加入到 Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于 3 秒。
setTimeout(fn,0)指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。
// 代码 1
console.log('先执行这里');
setTimeout(() => {
  console.log('执行啦')
}, 0);
// 输出结果:
// 先执行这里
// 执行啦
// 代码 2
console.log('先执行这里');
setTimeout(() => {
  console.log('执行啦')
}, 3000);
// 输出结果:
// 先执行这里
// ... 3s later
// 执行啦
四、setInterval
setInterval会每隔指定的时间将注册的函数置入 Event Queue,如果前面的任务耗时太久,那么同样需要等待。
唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入 Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。
五、除了广义的同步任务和异步任务,对任务还有更精细的定义。
- macro-task(宏任务):包括整体代码 script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick
不同类型的任务会进入对应的 Event Queue,比如setTimeout和setInterval会进入相同的 Event Queue。
事件循环的顺序,决定 JS 代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
setTimeout(function() {
    console.log('setTimeout');
})
new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})
console.log('console');
// 输出:promise console setTimeout
这段代码作为宏任务,进入主线程。
1、先遇到setTimeout,那么将其回调函数注册后分发到宏任务 Event Queue。(注册过程与上同,下文不再描述)
2、接下来遇到了Promise,new Promise立即执行,then函数分发到微任务 Event Queue。
3、遇到console.log(),立即执行。
4、整体代码 script 作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务 Event Queue 里面,执行。
5、第一轮事件循环结束,开始第二轮循环,当然要从宏任务 Event Queue 开始。我们发现了宏任务 Event Queue 中setTimeout对应的回调函数,立即执行。
6、结束。

console.log('1');
setTimeout(function() {
  console.log('2');
  process.nextTick(function() {
    console.log('3');
  })
  new Promise(function(resolve) {
    console.log('4');
    resolve();
  }).then(function() {
    console.log('5')
  })
})
process.nextTick(function() {
  console.log('6');
})
new Promise(function(resolve) {
  console.log('7');
  resolve();
}).then(function() {
  console.log('8')
})
setTimeout(function() {
  console.log('9');
  process.nextTick(function() {
    console.log('10');
  })
  new Promise(function(resolve) {
    console.log('11');
    resolve();
  }).then(function() {
    console.log('12')
  })
})
第一轮事件循环流程分析如下:
- 整体 script 作为第一个宏任务进入主线程,遇到console.log,输出 1。
- 遇到setTimeout,其回调函数被分发到宏任务 Event Queue 中。我们暂且记为setTimeout1。
- 遇到process.nextTick(),其回调函数被分发到微任务 Event Queue 中。我们记为process1。
- 遇到Promise,new Promise直接执行,输出 7。then被分发到微任务 Event Queue 中。我们记为then1。
- 又遇到了setTimeout,其回调函数被分发到宏任务 Event Queue 中,我们记为setTimeout2。
| 宏任务 Event Queue | 微任务 Event Queue | 
|---|---|
| setTimeout1 | process1 | 
| setTimeout2 | then1 | 
- 上表是第一轮事件循环宏任务结束时各 Event Queue 的情况,此时已经输出了 1 和 7。
- 我们发现了process1和then1两个微任务。
- 执行process1,输出 6。
- 执行then1,输出 8。
第一轮事件循环正式结束,这一轮的结果是输出 1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:
- 首先输出 2。接下来遇到了process.nextTick(),同样将其分发到微任务 Event Queue 中,记为process2。new Promise立即执行输出 4,then也分发到微任务 Event Queue 中,记为then2。
| 宏任务 Event Queue | 微任务 Event Queue | 
|---|---|
| setTimeout2 | process2 | 
| then2 | 
- 第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。
- 输出 3。
- 输出 5。
- 第二轮事件循环结束,第二轮输出 2,4,3,5。
- 第三轮事件循环开始,此时只剩setTimeout2了,执行。
- 直接输出 9。
- 将process.nextTick()分发到微任务 Event Queue 中。记为process3。
- 直接执行new Promise,输出 11。
- 将then分发到微任务 Event Queue 中,记为then3。
| 宏任务 Event Queue | 微任务 Event Queue | 
|---|---|
| process3 | |
| then3 | 
- 第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
- 输出 10。
- 输出 12。
- 第三轮事件循环结束,第三轮输出 9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为 1,7,6,8,2,4,3,5,9,11,10,12。