使用 Event Loop
Last updated
Last updated
JavaScript 的 runtime model(运行时模型)是基于 event loop(事件循环)的 。event loop 负责 execute 代码、collect 和 process 事件、execute queued sub-tasks。
关于 runtime 的理论模型,如下:
对象在堆中分配,堆只是一个名称,表示一个大的(大部分是非结构化的)内存区域。
function calls(函数调用)形成一个 frames stack。
JavaScript runtime 会使用一个 message queue 来表示要处理的消息列表,每个 message 都有一个关联的 function 来处理它。
在 event loop 期间的某个时刻,runtime 会开始处理 queue 里的 message,从队头(queue,先进先出)取一个。即把那个 message 从 queue 中移除,同时将 message 作为输入参数调用它所对应的 function。为了执行 function,就会给它创建一个新的 stack frame。
直到 stack 再次空了,对 function 的处理才会停止。接着,event loop 会处理 queue 里的下一个 message(如果有的话)。
之所以叫 event loop 就是因为它是一个处理 event 的 loop,其实现通常类似于:
每个 message 被处理完之后,才会处理下一个 message。
无论一个 function 什么时候运行,它都不能被抢占,在它运行完之前任何其它代码都不能被执行。 这一点和 C 语言不同,在 C 语言中,当一个 function 正在一个 thread 中运行着,它可能随时被 runtime system 停止以运行另一个 thread 中的其它代码。(嗯~ JS 语言是单线程的原因)
这种 model 的缺点是,如果一个 message 需要执行很长时间,要么 web application 将无法处理 user interactions(比如 click, scoll)。 此时,浏览器会弹个对话框以提示用户“a script is taking too long to run”。 好的做法是:缩短 message 的处理时间,(如果可能)也可以把它拆成多个 messages。
在 web browser 中,只要 event 发生,就会添加 message,并且会为其附加一个 event listener。 因此,click 一个具有 click event handler 的元素就会添加一个 message,其它 event 也类似。
函数 setTimeout
有两个参数:要添加到 queue 里的 message 和一个 time value(可选,默认是 0)。 time value 表示该 message 被 push 到 queue 里的(minimum)delay。 如果 queue 里没有其它 message 并且 stack 是空,那么 message 就会在 delay 之后被立即处理。 然而,如果 queue 里还有其它 message,那么 setTimeout
的 message 将不得不等待它前面的 messages 都被处理完了。 正因为这样,第二个参数 time value 表示的是 minimum time(最短时间)而不是 guaranteed time(保证时间)。
0 delay 并不意味着回调将在 0 毫秒之后触发。 以 0 毫秒的 delay 调用 setTimeout
不会在给定的 interval(时间间隔)之后执行 callback function。 执行取决于 queue 里等待 tasks 的数量。
在下面的示例中,消息“这只是一条消息”将在回调中的消息得到处理之前写入控制台,因为延迟是运行时处理请求所需的最短时间(不是保证时间) .
setTimeout 需要等待排队消息的所有代码完成,即使您为 setTimeout 指定了特定的时间限制。
多个 runtime 彼此通信
web worker 和 cross-origin iframe
都是独立的 runtime,它们有自己的 stack, heap 和 message queue。 两个不同的 runtimes 要通信,只能通过 postMessage
方法互发 messages。 postMessage
方法会向其它 runtime 添加一个 message,如果后者监听了 message
event。
JavaScript 中的 event loop model 一个非常有趣的特性是:never blocks(它从不阻塞)。 它处理 I/O 通常是通过 events 和 callbacks 执行的,所以当 application 正在等待 IndexedDB 查询的结果或是 XHR 请求的返回时, 它依然能处理其它事情,比如 user input。
不过还是存在一些历史异常情况,比如 alert
和同步 XHR,所以尽量避免使用它们。