如图,主线程运行的时候,会产生堆(heap)和栈(stack),其中堆为内存、栈为函数调用栈。我们能看到,Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务,具体包括以下过程。
- JavaScript 有一个主线程和调用栈,所有的任务最终都会被放到调用栈等待主线程执行。
- 同步任务会被放在调用栈中,按照顺序等待主线程依次执行。
- 主线程之外存在一个回调队列,回调队列中的异步任务最终会在主线程中以调用栈的方式运行。
- 同步任务都在主线程上执行,栈中代码在执行的时候会调用浏览器的 API,此时会产生一些异步任务。
- 异步任务会在有了结果(比如被监听的事件发生时)后,将异步任务以及关联的回调函数放入回调队列中。
- 调用栈中任务执行完毕后,此时主线程处于空闲状态,会从回调队列中获取任务进行处理。
上述过程会不断重复,这就是 JavaScript 的运行机制,称为事件循环机制(Event Loop)。
微任务
microTask
promise process.nextTick MutationObserver
宏任务
macroTask
setTimeout setInterval script setImmediate(可能会被废弃,非标准实现) I/0 requestAnimationFrame
UI 渲染(浏览器)、
为什么要将异步任务分为宏任务和微任务呢?
这是为了避免回调队列中等待执行的异步任务(宏任务)过多,导致某些异步任务(微任务)的等待时间过长。在每个宏任务执行完成之后,会先将微任务队列中的任务执行完毕,再执行下一个宏任务。
在浏览器的异步回调队列中,宏任务和微任务的执行过程如下:
- 宏任务队列一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务。
- 微任务队列中所有的任务都会被依次取出来执行,直到微任务队列为空。
- 在执行完所有的微任务之后,执行下一个宏任务之前,浏览器会执行 UI 渲染操作、更新界面。
在浏览器中每个宏任务执行完成后,会执行微任务队列中的任务。而在 Node.js 中,事件循环分为 6 个阶段,微任务会在事件循环的各个阶段之间执行。也就是说,每当一个阶段执行完毕,就会去执行微任务队列的任务。
这一次,彻底弄懂 JavaScript 执行机制