深入理解JavaScript的Event Loop事件循环机制为了在单线程环境中高效地管理这些异步操作,JavaScri
JavaScript是一门单线程的编程语言,这意味着在任何时刻,JS引擎只能执行一个任务。然而,在实际应用中,我们经常需要处理异步操作,比如网络请求、定时器、用户交互等。为了在单线程环境中高效地管理这些异步操作,JavaScript引入了Event Loop(事件循环)机制。本文将深入探讨JavaScript的Event Loop机制。
JavaScript是单线程语言
单线程意味着JS引擎在任意时刻只能执行一个任务。这与多线程语言不同,多线程语言可以同时执行多个任务。在单线程环境中,所有任务都是按顺序执行的,当前任务执行完毕后,才会执行下一个任务。
执行顺序和Event Loop
尽管JavaScript是单线程的,但其执行顺序并不总是按照代码的书写顺序。这是因为JS引入了Event Loop机制来管理任务的执行顺序。
Event Loop的基本原理
Event Loop是JavaScript的执行机制,它通过一个循环来监控和执行任务。以下是Event Loop的基本执行步骤:
- 执行全局脚本:首先,整个脚本(即所有代码)作为一个宏任务被执行。这是最初始的任务。
- 快速执行同步任务:在全局脚本执行过程中,所有同步任务会立即执行完毕。
- 异步任务登记:对于异步任务(如定时器、事件处理器、网络请求等),它们会被挂起并登记在Event Table中,等到触发条件满足时再被放入Event Queue。
- 进入空闲状态:当同步任务执行完毕后,JS引擎进入空闲(idle)状态,此时没有任务在执行。
- 检测Event Table:引擎会持续检测Event Table,查看是否有异步任务需要执行。
- 任务放入Event Queue:一旦Event Table中的任务满足执行条件(如定时器到期、网络请求完成),该任务会被移动到Event Queue中,等待执行。
- 执行Event Queue中的任务:Event Loop会按照先进先出的顺序,从Event Queue中取出任务并执行。
- 重复循环:上述步骤会不断重复,直到Event Table和Event Queue都为空,JavaScript引擎再次进入空闲状态。
简单来说,就是先执行完所有同步任务,再执行异步任务。
宏任务与微任务
宏任务与微任务的分类
宏任务(macro task)
宏任务是指那些可以包含其他任务(如微任务)并能被事件循环调度的任务。
- script:整体代码执行。
- setTimeout:延时执行任务。
- setInterval:周期执行任务。
- setImmediate:立即执行任务(Node.js环境中)。
- I/O操作:文件读写、网络请求等。
微任务(micro task)
微任务也是异步任务的一种,但是它们的执行优先级高于宏任务,并且在一个宏任务的生命周期内可以多次执行。
- Promise回调:异步操作的回调函数。
- process.nextTick:Node.js环境中的微任务。
- MutationObserver:观察DOM变化的回调函数。
宏任务与微任务的执行
- 宏任务(macro task):每次执行栈为空时,Event Loop会从宏任务队列中取出第一个任务并执行。
- 微任务(micro task):在每个宏任务执行完毕后,Event Loop会执行当前微任务队列中的所有任务。
事件循环过程
了解了宏任务和微任务后,我们再来看看事件循环的执行过程:
初始状态
- 调用栈为空,栈底是全局作用域。
- 微任务队列为空。
- 宏任务队列中只有一个
<script>
脚本(即整体代码)。
执行过程
- 全局上下文推入调用栈:当JavaScript引擎开始执行代码时,全局上下文会被推入调用栈,开始执行同步代码。
- 同步执行代码:在同步代码执行过程中,如果产生了宏任务或微任务,这些任务会分别加入各自的任务队列。
- 移除宏任务队列中的
<script>
脚本:当全局代码执行完毕后,<script>
脚本会被从宏任务队列中移除。 - 事件循环检查队列:Event Loop会检查宏任务和微任务队列,确保两个队列都清空。
- 执行微任务:在每个宏任务执行完毕后,Event Loop会执行微任务队列中的所有任务。
- 执行渲染操作:当所有任务队列都清空后,浏览器会执行渲染操作,更新页面。
- 循环往复:上述步骤会不断循环,直到所有任务都被执行完毕。
Event Loop的示例代码
通过一个复杂的例子来理解Event Loop的工作机制:
console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
})
.then(() => {
console.log('Promise 2');
});
console.log('Script end');
// 输出结果:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout
在这个例子中,console.log('Script start')
和console.log('Script end')
是同步任务,会立即执行。setTimeout
是宏任务,会在下一次事件循环时执行。Promise
的回调是微任务,会在当前宏任务script
执行完毕后立即执行。因此,输出顺序为:Script start
、Script end
、Promise 1
、Promise 2
、setTimeout
。
结语
JavaScript的Event Loop机制是理解其异步编程模型的关键。通过Event Loop,JavaScript能够在单线程环境中高效地管理异步操作,实现非阻塞的编程模型。宏任务和微任务分别管理不同的异步任务类型,确保任务在适当的时机被执行。希望通过本文的讲解,你能够更好地理解JavaScript的Event Loop机制。
转载自:https://juejin.cn/post/7416625240158208052