Javascript 并发模型与事件循环

Javascript 作为单线程的脚本语言,通过事件循环机制(event-loop)实现其 “非阻塞” 的特性。

运行时概念

函数的调用会形成栈帧,最外层函数的栈帧被压入栈的最底层,当最外层的函数返回后,栈就空了。

堆表示一个大部分非结构化的内存区域,对象被分配在堆中。

队列

JavaScript 运行时包含了一个待处理的消息队列。队列中的每个消息都有对应的处理函数。

事件循环

事件循环的实现简单来说如下:

while (queue.waitForMessage()) {
    queue.processNextMessage();
}

执行至完成

每一个消息完整的执行后,其它消息才会被执行。

添加消息

当一个事件出现且有一个事件监听器被绑定时,消息会被随时添加。

如果没有事件监听器,事件会丢失。

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。

这个时间段作为函数的第二个参数被传入,时间参数的值代表了消息被实际加入到队列的最小延迟时间。

如果队列中没有其它消息,消息会被马上处理。

零延迟

在零延迟调用 setTimeout 时,其并不是过了给定的时间间隔后就马上执行回调函数。

其等待的时间基于队列里正在等待的消息数量。

多个运行时互相通信

一个 web worker 或者一个跨域的iframe都有自己的栈,堆和消息队列。

两个不同的运行时只能通过 postMessage方法进行通信。

如果后者侦听到message事件,则此方法会向其他运行时添加消息。

其他

阻塞与死循环

由于 “执行至完成” 的特性,当一个消息需要太长时间才能完成,如遇到了死循环,Web应用无法处理用户的交互,例如点击或滚动。

Q:这时 Js 的执行状态是否是阻塞的? 是否与 JavaScript 非阻塞的特性矛盾?

A:不矛盾。阻塞是等待响应,代码处于停止状态。死循环是执行重复的代码导致耗尽计算机资源。