事件循环(EventLoop)是JavaScript异步编程背后的秘密。JS在一个线程上执行所有的操作,但是使用了一些聪明的数据结构,给了我们多线程的错觉。
调用栈(Call Stack)
调用堆栈负责跟踪要执行的所有操作。当一个函数完成时,它将从堆栈中弹出。
事件队列(Event Queue)
事件队列负责发送新函数到调用栈中处理。它遵循队列的数据结构特性(先进先出)来维护所有的操作按照正确的顺序执行。
当一个异步函数被调用时,他会被发送到对应的内置的浏览器API。这些API基于从调用栈收到的命令,启动各自的单线程操作。
比如:setTimeout。当堆栈中需要处理一个settimeout操作时,它会被派发到对应的API,并等到指定的时间将该操作送回来处理
API将操作派发到哪里了呢? ————事件队列。因此,在JS中我们有了一个执行异步操作的循环系统。JS本身是单线程的,但是浏览器API扮演了多线程的角色。
事件循环促进了这个过程;它不断地检查调用栈是否为空。如果为空,新函数将被推入事件队列;否则执行当前函数
深入理解js事件循环机制(浏览器篇)中有个动态图很形象:
一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。 setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。(引用自:github.com/Advanced-Fr…)
一次事件循环过程:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中,微任务队列在下一个宏任务之前,遇到宏任务则放在队列尾部
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
任务队列
通常情况下,任务队列中可能存在多个宏任务,但只有一个微任务队列。
宏任务(macro task / task)
通常指浏览器定义的行为 常见宏任务包括:
script(整块代码)- 计时器(
setTimeout、setInterval) I/O操作DOM事件(UI交互事件)setImmediate(node环境)postMessageMessageChannel
微任务(micro task / job)
当前宏任务执行结束后立即执行 包括:
Promise.then/catch/finallyprocess.nextTick(node中优先级比promise回调更高的微任务)
- 宏任务
- 微任务
- 异步promise面试题
自用笔记,看客留情,仍有待拓展