Javascript 作为单线程的脚本语言,通过事件循环机制(event-loop)实现其 “非阻塞” 的特性。
运行时概念
栈
函数的调用会形成栈帧,最外层函数的栈帧被压入栈的最底层,当最外层的函数返回后,栈就空了。
堆
堆表示一个大部分非结构化的内存区域,对象被分配在堆中。
队列
JavaScript 运行时包含了一个待处理的消息队列。队列中的每个消息都有对应的处理函数。
事件循环
事件循环的实现简单来说如下:
while (queue.waitForMessage()) {
queue.processNextMessage();
}
执行至完成
每一个消息完整的执行后,其它消息才会被执行。
添加消息
当一个事件出现且有一个事件监听器被绑定时,消息会被随时添加。
如果没有事件监听器,事件会丢失。
调用 setTimeout
函数会在一个时间段过去后在队列中添加一个消息。
这个时间段作为函数的第二个参数被传入,时间参数的值代表了消息被实际加入到队列的最小延迟时间。
如果队列中没有其它消息,消息会被马上处理。
零延迟
在零延迟调用 setTimeout
时,其并不是过了给定的时间间隔后就马上执行回调函数。
其等待的时间基于队列里正在等待的消息数量。
多个运行时互相通信
一个 web worker 或者一个跨域的iframe
都有自己的栈,堆和消息队列。
两个不同的运行时只能通过 postMessage
方法进行通信。
如果后者侦听到message
事件,则此方法会向其他运行时添加消息。
其他
阻塞与死循环
由于 “执行至完成” 的特性,当一个消息需要太长时间才能完成,如遇到了死循环,Web应用无法处理用户的交互,例如点击或滚动。
Q:这时 Js 的执行状态是否是阻塞的? 是否与 JavaScript 非阻塞的特性矛盾?
A:不矛盾。阻塞是等待响应,代码处于停止状态。死循环是执行重复的代码导致耗尽计算机资源。