WTF JavaScript 极简教程 23. 事件循环
WTF JavaScript 教程,帮助新人快速入门 JavaScript。
推特:@WTFAcademy_ | @0xAA_Science
WTF Academy 社群: 官网 wtf.academy | WTF Solidity 教程 | discord | 微信群申请
所有代码和教程开源在 github: github.com/WTFAcademy/WTF-Javascript
尽管 JavaScript 是单线程的编程语言,但是它可以通过事件循环机制处理并发操作,使 JavaScript 能够进行异步处理。JavaScript 将代码分为同步任务和异步任务,通过事件循环,异步任务在等待(如网络请求)期间不会阻塞主线程,可以同时执行其他任务。
调用栈和任务队列
在我们深入研究事件循环之前,我们需要理解以下两个概念:
调用栈(Call Stack):JavaScript只有一个调用栈,用于在代码执行时跟踪函数调用的位置。当函数被调用时,它被添加到堆栈的顶部。当函数返回时,它从堆栈中被移除。同步任务会直接进入调用栈。
任务队列(Task Queue):当异步任务(如setTimeout或fetch)完成时,它们的回调函数被添加到任务队列中。如果调用栈为空,事件循环会将这些回调函数一个接一个地移到调用栈中以便执行。
宏任务和微任务
在 JavaScript 的事件循环机制中,任务被分为两种类型:宏任务和微任务。
宏任务(Macrotask):由 JavaScript 引擎线程直接执行的任务,包括整个脚本(main script),setTimeout 和 setInterval 的回调,setImmediate(Node.js环境)等。
微任务(Microtask):微任务是在当前宏任务结束后立即执行的任务,包括 Promise 的 then 和 catch 的回调,process.nextTick(Node.js环境),MutationObserver的回调(浏览器环境)等。
事件循环过程
事件循环的过程可以简化为以下
几个步骤:
- 从宏任务队列中取出一个任务来执行。
- 执行完这个任务后,执行所有的微任务。
- 当微任务队列清空后,进入下一次事件循环,执行下一个宏任务。
来看一个例子,展示了宏任务和微任务的执行顺序:
console.log('script start'); // Macrotask
setTimeout(function() {
console.log('setTimeout'); // Macrotask
}, 0);
Promise.resolve().then(function() {
console.log('promise1'); // Microtask
}).then(function() {
console.log('promise2'); // Microtask
});
console.log('script end'); // Macrotask
上述代码的输出顺序为:
script start
script end
promise1
promise2
setTimeout
解释:
- 首先,代码执行到
console.log('script start')
,输出 "script start"。 - 然后,遇到
setTimeout
,将其回调函数推入宏任务队列中。 - 接着,遇到
Promise.resolve().then()
,将第一个then
回调函数推入微任务队列中。 - 继续执行,遇到第二个
then
回调函数,将其推入微任务队列中。 - 执行到
console.log('script end')
,输出 "script end"。 - 当前宏任务(script主线程代码)执行完毕,事件循环开始处理微任务队列,按顺序执行微任务。
- 执行第一个微任务,输出 "promise1"。
- 执行第二个微任务,输出 "promise2"。
- 微任务执行完毕,事件循环开始处理下一个宏任务。
- 从宏任务队列中取出
setTimeout
的回调函数,输出 "setTimeout"。
建议你在 jsv9000 网站上体验一下事件循环的可视化,可以更直观的理解事件循环的过程。
总结
在这一讲,我们深入学习了 JavaScript 中的事件循环,包括宏任务和微任务。这是理解 JavaScript 异步编程的基础,能帮助我们更好地理解和控制代码的执行顺序。