什么是事件循环(Event Loop)?浏览器和 Node.js 中的事件循环有什么区别?
首先,JavaScript
是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环。
在JavaScript
中,所有的任务都可以分为
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如
ajax
网络请求,setTimeout
定时函数等
什么是事件循环(Event Loop)?
事件循环是 JavaScript 实现异步编程的核心机制之一,用于协调同步代码、异步任务(宏任务、微任务)的执行顺序,避免代码阻塞。
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环。
详细说就是:先进行同步任务、执行过程中遇到宏任务或微任务放到各自队列。同步任务执行完了以后先查看微任务队列中有没有任务,执行队列所有任务以后再取一个宏任务进行执行,这样一个循环往复的过程就是事件循环。管理异步API的回调函数什么时候回到主线程中执行。
事件循环的组成部分
调用栈(Call Stack):JavaScript引擎用来跟踪函数调用和返回值的栈结构。
任务队列(Task Queues):存储待执行的异步任务。不同的异步任务会被放入不同的队列中,如宏任务队列(Macro Task Queue)和微任务队列(Micro Task Queue)。
事件循环(Event Loop):不断检查调用栈和任务队列,确保任务按顺序执行。
浏览器和 Node.js 中的事件循环有什么区别?
浏览器中的事件循环:
- 执行同步代码,遇到异步任务(宏任务、微任务)分别放入对应队列;
- 同步代码执行完毕后,清空所有微任务队列(按顺序执行);
- 微任务执行完毕后,执行一个宏任务,然后再次清空微任务队列,循环往复。
- 宏任务:setTimeout、setInterval、DOM 事件、AJAX 请求;
- 微任务:Promise.then/catch/finally、async/await、MutationObserver。
微任务通常比宏任务有更高的优先级,会在当前宏任务执行完毕后立即执行,但在下一个宏任务开始之前。
Node.js 中的事件循环:
在Node中的事件循环分为六个阶段:
- timer定时器阶段:执行如setTimeout和setInterval等的回调函数
- close callbacks关闭回调阶段:执行socket.close()事件回调
- check检查阶段:执行setImmediate的回调函数
- Poll轮询阶段:这是一个至关重要的阶段,系统主要做两件事,一是回到timer阶段执行回调,二是执行I/O回调。会主动检测是否有新的I/O事件,若存在新的I/O事件,则执行其回调函数,适当的条件下,node将阻塞在这里。
- Idle闲置阶段:仅供系统内部使用
- I/O回调 阶段:执行上一轮循环中未执行的I/O回调函数
- 微任务:Promise.then/catch/finally、process.nextTick、queueMicrotask
- 宏任务:setlnterval、setimeout、setlmmediate、I/O事件、close事件
注意:浏览器的事件循环和node事件循环有什么区别?
1、 微任务队列执行时机不同。在浏览器事件循环中, 每执行完一个宏任务,便要检查并执行微任务队列、而node事件循环中microtask 微任务会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 微任务队列的任务。
2、 特定API:Node.js有process.nextTick和setImmediate,而浏览器没有
浏览器事件循环流程:执行一个宏任务 -> 清空所有微任务 -> 执行下一个宏任务 -> 清空所有微任务 -> ...
Node.js 事件循环流程:执行完Timers阶段的所有宏任务 -> 清空所有微任务 -> 进入下一个阶段...
简单来说:
- 浏览器是一个宏任务+一个微任务队列
- node是一个宏任务队列+一个微任务队列
例子:
setTimeout(()=>{console.log('timer1')Promise.resolve().then(function() {console.log('promise1')})
}, 0)
setTimeout(()=>{console.log('timer2')Promise.resolve().then(function() {console.log('promise2')})
}, 0)浏览器端运行结果:timer1=>promise1=>timer2=>promise2
Node 端运行结果:timer1=>timer2=>promise1=>promise2
console.log('script开始');
setTimeout(() => {console.log('宏任务1');Promise.resolve().then(function () {console.log('微任务2')})
},0);setTimeout(() => {console.log('宏任务2');Promise.resolve().then(function() {console.log('微任务3')})
})Promise.resolve().then(function () {console.log('微任务1');
})console.log('script结束');//浏览器端运行结果
script开始
script结束
微任务1
宏任务1
微任务2
宏任务2
微任务3//node端运行结果
script开始
script结束
微任务1
宏任务1
宏任务2
微任务2
微任务3