# 使用 Event Loop

JavaScript 的 runtime model（运行时模型）是基于 event loop（事件循环）的 。event loop 负责 execute 代码、collect 和 process 事件、execute queued sub-tasks。

## 1. JavaScript 的单线程

## 2. runtime 的相关概念

关于 runtime 的理论模型，如下：

![](https://2598460105-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGNkDWo1TzHEOBRUxCRfy%2Fuploads%2FUQ0nhHplbe217v4jfIqd%2Fimage.png?alt=media\&token=9108fb4a-ce10-487e-81f6-273999acabc6)

### 2.1 heap

对象在堆中分配，堆只是一个名称，表示一个大的（大部分是非结构化的）内存区域。

### 2.2 stack

function calls（函数调用）形成一个 frames stack。

### 2.3 queue

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（如果有的话）。

## 3. Event loop

之所以叫 event loop 就是因为它是一个处理 event 的 loop，其实现通常类似于：

```js
// 同步等待 message 的到来（??? 莫非这里就是指的 JS 的主线程）
while (queue.waitForMessage()) {
    // 取出一个，来执行
    queue.processNextMessage()
}
```

### "Run-to-completion"

每个 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。

### Adding 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（保证时间）。

```js
const now = new Date().getTime() / 1000;

// 设置的是在 500 毫秒之后执行，但真正执行的时候是在 2+ 秒之后
setTimeout(() => {
    // 会输出：在 2 秒之后运行
    console.log(`在 ${new Date().getTime() / 1000 - now} 秒之后运行`);
}, 500);

while (true) {
    // 模拟一个需要 2 秒的操作
    if (new Date().getTime() / 1000 - now >= 2) {
        break
    }
}
```

### Zero delays

0 delay 并不意味着回调将在 0 毫秒之后触发。 以 0 毫秒的 delay 调用 `setTimeout` 不会在给定的 interval（时间间隔）之后执行 callback function。 执行取决于 queue 里等待 tasks 的数量。

在下面的示例中，消息“这只是一条消息”将在回调中的消息得到处理之前写入控制台，因为延迟是运行时处理请求所需的最短时间（不是保证时间） .

setTimeout 需要等待排队消息的所有代码完成，即使您为 setTimeout 指定了特定的时间限制。

```js
(() => {
    console.log('start')

    setTimeout(() => {
        console.log('this is a message from call back 1')
    }) // time value 默认是 0

    console.log('this is just a message')

    setTimeout(() => {
        console.log('this is a message from call back 2')
    }, 0)

    console.log('end')
})()

// start
// this is just a message
// end
// this is a message from call back 1
// this is a message from call back 2
```

### Several runtimes communicating together

多个 runtime 彼此通信

web worker 和 cross-origin `iframe` 都是独立的 runtime，它们有自己的 stack, heap 和 message queue。 两个不同的 runtimes 要通信，只能通过 `postMessage` 方法互发 messages。 `postMessage` 方法会向其它 runtime 添加一个 message，如果后者监听了 `message` event。

## 4. Never blocking

JavaScript 中的 event loop model 一个非常有趣的特性是：never blocks（它从不阻塞）。 它处理 I/O 通常是通过 events 和 callbacks 执行的，所以当 application 正在等待 IndexedDB 查询的结果或是 XHR 请求的返回时， 它依然能处理其它事情，比如 user input。

不过还是[存在一些历史异常情况](https://stackoverflow.com/questions/2734025/is-javascript-guaranteed-to-be-single-threaded/2734311#2734311)，比如 `alert` 和同步 XHR，所以尽量避免使用它们。
