一、了解事件循环
事件循环用于协调异步任务的执行顺序、传递消息以及处理用户交互等事件。由以下四个组成部分组成。
-
调用栈(Call Stack)
调用栈是 JavaScript 的一种执行机制,用于控制函数的执行顺序。当函数被调用时,将函数压入调用栈中,并开始执行函数,函数执行完成后从调用栈中弹出并返回结果。如果调用栈为空,则脚本停止执行。
-
任务队列(Task Queue)
任务队列是 JavaScript 的一种执行机制,用于存储异步任务的回调函数。当异步任务完成时,将回调函数添加到任务队列中等待被执行。
-
事件循环(Event Loop)
事件循环是 JavaScript 的一种执行机制,用于协调调用栈和任务队列的执行顺序。事件循环不断地从任务队列中取出第一个回调函数,并将其压入调用栈中执行,直到任务队列为空为止。
-
微任务队列(Microtask Queue)
微任务队列是在事件循环的回调函数执行过程中产生的,用于存储微任务的回调函数。当微任务产生后,会被添加到微任务队列中,等待事件循环调用栈为空时执行。微任务包括 Promise、MutationObserver 等。
二、宏任务和微任务
宏任务和微任务都属于异步任务的一种,它们的区别在于任务队列不同,执行的时机也不同。
宏任务(Macro Task)
宏任务指的是那些需要长时间运行的任务,每个宏任务执行完成后,都会清空微任务,然后从宏任务队列中取出下一个任务执行。
- setInterval() / setTimeout()
- setImmediate()
- ajax
- 事件绑定
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
- script (可以理解为外层同步代码)
微任务(Micro Task)
微任务指的是那些需要尽可能快速执行的任务,当每个宏任务执行完毕并清空微任务队列,会接着执行当前微任务队列中的任务,直到执行完毕为止。
- new Promise()后的then()与catch()函数
- new MutaionObserver()
- process.nextTick(Nodejs)
- Object.observe(已废弃;Proxy 对象替代)
三、JavaScript 中任务的执行顺序如下:
- 执行同步任务,即按照顺序执行的任务。
- 执行当前宏任务队列中的第一个任务,例如
setTimeout、setInterval、setImmediate等。 - 执行所有可立即执行的微任务队列中的任务,例如
Promise的then()和catch()、process.nextTick等。 - 回到宏任务队列中,执行下一个宏任务,重复步骤 2 和 3。
四、巩固练习
示例一:async await 相关
不管await后面跟着的是什么,await都会阻塞后面的代码 (即加入微任务队列),先执行 async外面的同步代码,同步代码执行完,再回到 async 函数中,再执行之前阻塞的代码
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
上述输出结果为:1,fn2,3,2
示例二:同步、宏任务、微任务输入顺序
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')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
上述输出结果为:script start、async1 start、async2、promise1、script end、async1 end、promise2、settimeout