一、什么是Event Loop
首先,需要了解的是,JavaScript是单线程语言,意味着它只有一个执行线程,等到上一个任务结束后才会执行下一个任务。如果上一个任务耗时过长,那下一个任务也要持续从而等待造成任务阻塞。为了解决这个问题,JavaScript有一个基于Event Loop(事件循环机制)的并发模型,负责执行代码、收集和处理事件以及执行队列中的子任务。
二、同步任务和异步任务
JavaScript单线程任务被分为同步任务和任务异步任务。
- 同步任务:指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是处于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。
- 异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
在异步任务中,Event Table可以理解成一张事件和回调函数对应表,它就是用来存储 JavaScript 中的异步事件及其对应的回调函数的列表。Event Queue简单理解就是回调函队列,所以它也叫 Callback Queue。当 Event Table 中的事件被触发,事件对应的回调函数就会被推进这个 Event Queue,然后等待被执行。
三、宏任务和微任务
Event Loop是通过任务队列的机制来进行协调的,任务队列里面是各种需要处理当前程序处理的异步任务。在JavaScript,异步任务一般包括宏任务和微任务。
- 宏任务:主线程上的任务,它们会在主线程上执行。常见的宏任务包括 setTimeout, setInterval, setImmediate等。宏任务一般是与浏览器的渲染相关的任务。
- 微任务:在主线程上执行的任务之前或之后立即执行的任务。常见的微任务包括then, process.nextTick, Object.observe, MutationObserver 等。微任务一般是与 JavaScript代码的执行相关的任务。
四、运行原理
- 运行同步任务, 微任务加入队列, 等待同步任务执行完毕
- 查看是否有微任务, 若有则执行, 按照先进先出的规则进行
- 微任务结束后执行宏任务, 执行完每一个宏任务之后都会再次进入第 2 步流程
- 微任务和宏任务都执行完毕
五、例子说明
console.log(1)
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
})
})
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data)
})
setTimeout(() => {
console.log(6)
})
console.log(7)
// 输出结果:1,4,7,5,2,3,6
- 输出过程:
- 执行同步任务console.log(1);// 输出结果:1
- 遇到setTimeout,放到宏任务队列中(回调函数记为callback1);
- 执行同步任务console.log(4);// 输出结果:1,4
- 遇到Promise.then,放到微任务队列中(回调函数记为callback2);
- 遇到setTimeout,放到宏任务队列中(回调函数记为callback3);
- 执行同步任务console.log(7);// 输出结果:1,4,7
- 所有同步任务执行完毕,开始执行异步任务,先依次执行微任务队列中的任务,再依次执行宏任务队列的队伍;
- 执行微任务callback2,console.log(data);// 输出结果:1,4,7,5
- 所有微任务执行完毕,开始依次执行宏任务队列中的任务;
- 执行宏任务callback1,console.log(2);// 输出结果:1,4,7,5,2
- 执行宏任务callback1后遇到Promise.then,放到微任务队列中(回调函数记为callback4);
- 第一个宏任务执行完毕,再次依次执行微任务队列中的任务;
- 执行微任务callback4,console.log(3);// 输出结果:1,4,7,5,2,3
- 所有微任务再次执行完毕,开始依次执行宏任务队列中的任务;
- 执行宏任务callback3,console.log(6);// 输出结果:1,4,7,5,2,3,6
- 所有任务执行完毕。