JavaScript运行时
在执行 JavaScript 代码的时候,JavaScript 运行时实际上维护了一组用于执行 JavaScript 代码的代理。每个代理由一组执行上下文的集合、执行上下文栈、主线程、一组可能创建用于执行 worker 的额外的线程集合、一个任务队列以及一个微任务队列构成。除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其它组成部分对该代理都是唯一的。
事件循环(Event loops)
每个代理都是由 事件循环 驱动的,事件循环负责收集用户事件(包括用户事件以及其他非用户事件等)、对任务进行排队以便在合适的时候执行回调。然后它执行所有处于等待中的 JavaScript 任务(宏任务),然后是微任务,然后在开始下一次循环之前执行一些必要的渲染和绘制操作。
网页或者 app 的代码和浏览器本身的用户界面程序运行在相同的 线程中, 共享相同的 事件循环。 该线程就是 主线程,它除了运行网页本身的代码之外,还负责收集和派发用户和其它事件,以及渲染和绘制网页内容等。
然后,事件循环会驱动发生在浏览器中与用户交互有关的一切,但在这里,对我们来说更重要的是需要了解它是如何负责调度和执行在其线程中执行的每段代码的。
有如下三种事件循环:
Window 事件循环
window 事件循环驱动所有同源的窗口 (though there are further limits to this as described elsewhere in this article XXXX ????).
Worker 事件循环
**
worker 事件循环顾名思义就是驱动 worker 的事件循环。这包括了所有种类的 worker:最基本的 web worker 以及 shared worker 和 service worker。 Worker 被放在一个或多个独立于 “主代码” 的代理中。浏览器可能会用单个或多个事件循环来处理给定类型的所有 workder。
Worklet 事件循环
**
worklet 事件循环用于驱动运行 worklet 的代理。这包含了 Worklet、AudioWorklet 以及 PaintWorklet。
多个同源(译者注:此处同源的源应该不是指同源策略中的源,而是指由同一个窗口打开的多个子窗口或同一个窗口中的多个 iframe 等,意味着起源的意思,下一段内容就会对这里进行说明)窗口可能运行在相同的事件循环中,每个队列任务进入到事件循环中以便处理器能够轮流对它们进行处理。记住这里的网络术语 “window” 实际上指的用于运行网页内容的浏览器级容器,包括实际的 window,一个 tab 标签或者一个 frame。
在特定情况下,同源窗口之间共享事件循环,例如:
- 如果一个窗口打开了另一个窗口,它们可能会共享一个事件循环。
- 如果窗口是包含在
<iframe>中,则它可能会和包含它的窗口共享一个事件循环。 - 在多进程浏览器中多个窗口碰巧共享了同一个进程。
这种特定情况依赖于浏览器的具体实现,各个浏览器可能并不一样。
运行机制
js 的执行分为同步任务和异步任务,同步任务在主线程中执行,是一个栈结构,所以也被称为 同步执行栈,遵循先入后出的原则。
当同步执行栈遇到异步任务时(比如发起一个网络请求),就会先将异步任务挂起,等到异步事件执行完毕后,会被加入到事件队列中,也就是 Eventloop。Eventloop 是一个队列结构,它遵循先入先出的原则,还可以根据优先级的不同进行插队操作,这涉及到了任务队列的 宏任务 和 微任务,关于宏任务和微任务后面会讲到。
每一个异步任务都会对应一个回调函数,当同步执行栈执行完毕,主线程处于闲置状态时,会去异步队列里抽取最先被推入队列中异步事件的回调函数,压入执行栈中并执行。执行完毕后,如果主线程仍然空闲,继续拉取任务队列中的任务,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为事件循环(EventLoop)”的原因。
宏任务与微任务
setTimeout、async、await 都归属与异步任务中,异步任务也有区别,有早有晚,分为宏任务(macrotask)和微任务(microtask),微任务 优先于 宏任务执行。
微任务
- async
- Promise
- obseve
- process.nextTick(Node环境)
宏任务
- ajax
- event事件
- setTimeout
- setInterval
- setImmediate(Node环境)
- requestAnimationFrame
- I/O
-
宏任务队列与微任务队列的区别
当执行来自宏任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要 在下一次迭代开始之后才会被执行。
- 每次当一个任务退出且执行上下文为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,并在下一个任务开始执行之前且当前事件循环结束之前执行完所有的微任务。
简单点理解就是,宏任务队列在每执行完一个任务后都会先检查同步执行栈是否有要执行的代码,如果有的话先执行同步执行栈中的代码,如果没有的话才会拉取下一个宏任务,也就是说宏任务之间是有间歇的。而微任务队列中只要存在任务,不管有多少个,当主线程空闲之后都会一次性将微任务队列中的任务全部执行完毕,尽管在执行过程中有新的微任务加入,也会被一起执行。
