浅析 JS 中的 Event Loop

221 阅读3分钟

常规 Event Loop

这篇文章展示了给定的 JS 片段的调用堆栈,事件循环,和浏览器环境给出的异步 API 三者之间的关系和执行顺序。

---------------------------------------------------------------------

给出的 JS :

setTimeout(() => {
  console.log('hi')
}, 1000)

调用堆栈,事件循环和 Web APIs 存在如下关系:

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

起初,这三者都是空的。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

代码开始执行,并将一个 <global> 推进 Call Stack。注:<global> 可以理解为全局变量所处的执行环境。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | |               |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

第一行代码执行完毕,将会把函数执行作为第二项推进 Call Stack。值得注意的是 Call Stack 是一个堆栈,它遵循后进先出原则--推进去的最后一个元素将会成为弹出的第一个元素。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

执行 setTimeout 函数实际上调用了 JS 之外的代码。setTimeout 属于 Web API 的一部分,而 Web API 是由浏览器环境提供的。当然了,node 中也存在一套不同的 API。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <global>          |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

然后,setTimeout 语句执行完毕,它将工作交付 Web API 。Web API 将会等待指定的时长(1000ms)。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | | timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

此时,这里没有其他的 JS 需要执行了,Call Stack 现在是空的状态。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function   <-----timeout, 1000 |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

 一旦时延到达指定时长(1000ms),Web API 将会把代码加入到 Event Loop (事件循环)内,从而使得 JS 能够执行。

这里需要注意的是 Web API 并非直接将所要执行的代码推到 Call Stack中,因为这样做可能打断正在执行的代码,那会得到一个非常诡异的结果。

Event Loop 是一个队列。第一个被推进来的元素也会被首先推出去。遵从先入先出原则。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

当 Call Stack 内没有代码执行的时候, JS 执行环境将检查 Event Loop 内是否有等待执行的任务在排队。如果有,第一个元素将会被移动到 Call Stack 来执行。

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('hi') | console.log       |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |

箭头函数执行的结果就是调用了 console.log 。那么这句代码将被推到 Call Stack 内。(因为这是同步的API)

        [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi

console.log 执行完毕后,打印出 hi。Call Stack 同时移除这部分代码。

      [code]        |   [call stack]    | [Event Loop] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi

最后,箭头函数内没有其他的指令需要执行,它也被从 Call Stack 中移除。

我们的程序就此结束了运行。

End.


---------------------------------------------------------------------原文链接:Regular Event Loop

有任何建议或疑惑,欢迎在评论区留言。