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任务执行器或跨域iframe都拥有自己的堆栈,堆,和消息队列。两个不同的运行时只能通过postMessage方法发送消息进行通信。如果后者监听消息事件,则该方法向其他运行时添加消息。
从不堵塞
事件循环模型的一个非常有趣的特性是JavaScript从不阻塞,不像许多其他语言。处理I/O通常是通过事件和回调函数进行的,因此当应用程序等待一个IndexedDB查询返回或XHR请求返回时,它还可以处理其他事情,比如用户输入。
有遗留的例外存在,像警报或同步XHR,但它被认为是一个很好的实践来避免它们。请注意,异常的异常确实存在(但通常是实现错误,而不是其他)。
原文链接,翻译难免有出入,欢迎交流和斧正。