【译】并发模型和事件循环

JavaScript有一个基于“事件循环”的并发模型。这种模型完全不同于从其他语言的,如C和java。

运行时的概念

下面的章节解释一个理论模型。现代JavaScript引擎实现和优化所描述的语义。

直观表示

【译】并发模型和事件循环

函数调用形成一个堆栈的帧。

当调用bar函数时,第一个帧被创建,它包含了bar的参数和局部变量。当bar调用foo时,第二个帧被创建并被推到第一个帧上,该第一个帧包含foo的参数和局部变量。返回时,顶部帧元素从堆栈中弹出(只留下bar的调用帧)。当bar返回时,堆栈为空。 

 1 function foo(b) {
 2   var a = 10;
 3   return a + b + 11;
 4 }
 5 
 6 function bar(x) {
 7   var y = 3;
 8   return foo(x * y);
 9 }
10 
11 console.log(bar(7));

 对象在堆中分配,该堆只是一个名称,用来表示内存的一个很大的非结构化区域。

队列

JavaScript运行时包含消息队列,它是要处理的消息的列表。消息队列是一个与每个消息关联的函数。当堆栈有足够的容量时,将从队列中取出消息并进行处理。处理包括调用关联函数(从而创建初始堆栈帧)。当堆栈再次变空时,消息处理结束。 

事件循环

事件循环这个名字的由来,因为它通常是如何实现的,它通常类似于:

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

当queue.waitForMessage被处理完毕时,才处理queue.processNextMessage。

完全处理

在处理任何其他消息之前,每个消息都被完全处理。这特别有助于你推理程序。因为每当一个函数运行,它不能捷足先登,要将之前的任何其他代码完全运行(可以修改数据的函数处理)。这与C不同,例如,如果一个函数在一个线程中运行,它可以在任何点停止运行另一个线程中的其他代码。
此模型的一个缺点是,如果消息需要太长时间完成,Web应用程序无法处理用户交互,如单击或滚动。浏览器用“脚本运行太长”对话框来缓解这个问题。一个好的做法是使消息处理短,如果可能的话,削减一个消息到几个消息。

添加消息

在web浏览器中,任何时候一个事件发生时都会添加消息,并附加有事件侦听器。如果没有侦听器,则事件丢失。因此,单击带有单击事件处理程序的元素将添加一条消息,与其他事件相同。 
调用setTimeout将在指定的时间后(你设置的第二个参数)向队列中添加消息。如果在队列中没有其他消息,这消息会被立刻处理;不过,如果有消息,setTimeout消息将不得不等待其他要处理的消息先处理完毕。因此,第二个参数表示最小时间,而不是保证时间。

零延迟

零延迟实际上并不意味着在零毫秒后执行回调函数。调用setTimeout(零),不一定会在给定的时间间隔后执行回调函数。执行取决于队列中等待任务的数目。在下面的例子中的“this is just a message”将被先于它前面的setTimeout函数(0延迟)处理。因为延迟是运行时处理请求所需的最小时间,但不是保证时间。

 1 (function() {
 2     console.log('this is the start');
 3 
 4     setTimeout(function cb() {
 5         console.log('this is a msg from call back');
 6     });
 7 
 8     console.log('this is just a message');
 9     
10     setTimeout(function cb1() {
11         console.log('this is a msg from call back1');
12     }, 0);
13     console.log('this is the end');
14 })();
15 // "this is the start"
16 // "this is just a message"
17 // "this is the end"
18 // "this is a msg from call back"
19 // "this is a msg from call back1"

几个运行时之间的通信

一个web worker或跨域iframe都拥有自己的堆栈,堆,和消息队列。两个不同的运行时只能通过postMessage方法发送消息进行通信。如果后者监听消息事件,则该方法向其他运行时添加消息。

从不堵塞

事件循环模型的一个非常有趣的特性是JavaScript从不阻塞,不像许多其他语言。处理I/O通常是通过事件和回调函数进行的,因此当应用程序等待一个IndexedDB查询返回或XHR请求返回时,它还可以处理其他事情,比如用户输入。

有遗留的例外存在,像警报或同步XHR,但它被认为是一个很好的实践来避免它们。请注意,异常的异常确实存在(但通常是实现错误,而不是其他)。

 原文链接,翻译难免有出入,欢迎交流和斧正。