vlambda博客
学习文章列表

动图学 JavaScript 之:事件循环(Event Loop)

前言

今天该学习 Event Loop 啦,其实之前我写过一篇 Event Loop 的文章:

这篇呢则是动图学 JS 系列中的,可以结合之前的文章食用~

我们都知道 JavaScript 是一门 单线程 的语言:同一时间只能运行一个任务。通常情况下这没什么问题,但是如果你有一个任务需要耗费 30 秒的时间,那其他任务难道都要等它 30 秒么?(由于 JS 运行在浏览器的主线程,所以这 30 秒的时间里,整个页面都会处于卡死状态)

幸运的是,浏览器提供了一些 JS 引擎不具备的功能:Web API。它包括 DOM APIsetTimeoutHTTP 请求 等等。这些功能都可以帮助我们处理 异步、非阻塞 的操作。

调用栈

当我们调用一个函数时,它会被添加到一个叫做 调用栈 (call stack) 的地方,调用栈是 JS 引擎的一部分,而不是浏览器特有的。本质上它是一个栈,具有 后进先出 (Last In, First Out. 即 LIFO) 的特点。当一个函数调用完成,它就被从调用栈中弹出。

上图中函数 respond 返回了一个 setTimeout 函数,它也被添加到调用栈中,(setTimeout 正是 Web API 提供的功能之一:它可以让我们延迟一个任务的执行并且不阻塞主线程。)setTimeout 被调用之后,传给它的箭头函数 () => { return 'Hey' } 就被添加进了 Web API (此处简化了概念,具体可以看笔者的另一篇文章)中。同时 setTimeout 和 respond 函数从调用栈中弹出,它们都返回了相应的值。

动图学 JavaScript 之:事件循环(Event Loop)

任务队列

在 Web API 中,一个定时器已经创建,它将会等待 1000 ms,当时间到后,这个箭头函数并不会立即被调用栈执行,它会被添加到一个队列中,我们暂且称之为 任务队列 (原文中叫 Callback Queue)。

动图学 JavaScript 之:事件循环(Event Loop)

这里可能会让人困惑:那个回调箭头函数并不是在 1000ms 后被直接添加到 调用栈 的,而是被添加进了 任务队列。队列嘛,就是大家排队,先来的先服务,被谁服务?没错!就是调用栈。

事件循环

说了这么多,终于轮到我们的 Event Loop 登场了!如果上面的调用栈是一个银行窗口,任务队列中的回调函数是一个个排队办业务的人,那么 Event Loop 就是叫号系统!Event Loop 的唯一任务就是 连接任务队列和调用栈:

它不停检查 调用栈 中是否有任务需要执行,如果没有,就检查 任务队列,从中弹出一个任务,放入调用栈中,如此往复循环。

动图学 JavaScript 之:事件循环(Event Loop)

上图中终于轮到那个箭头函数接受调用了,它被调用完,也被弹出了,轻轻地它走了,只留下一个 Hey! o(╯□╰)o

动图学 JavaScript 之:事件循环(Event Loop)

一个例子

看图片是不是挺好理解的~ 那就来看一个例子,可以把下面的代码粘贴到浏览器的控制台亲自跑一下:

const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");

bar();
foo();
baz();

  1. 我们调用了函数 barbar 返回了一个 setTimeout 函数。

  2. setTimeout 中的回调函数被添加到 Web APIsetTimeout 函数和 bar 调用完成被从调用栈弹出。

  3. 定时器开始,同时函数 foo 被调用,打印出 Firstfoo 函数返回 undefined

  4. 函数 baz 被调用,打印出 Third

  5. 500ms 定时器结束,回调函数被放入任务队列,Event Loop 检查到调用栈是空的,所以将其取出放在调用栈。

  6. 回调函数被执行,打印出 Second


全文就到这里啦,希望对你理解 Event Loop 有所帮助~

本文是翻译的系列文章:

  • 动图学 JS 之:事件循环(Event Loop)【本篇】

  • 动图学 JS 之:JavaScript 引擎 【Pending】


欢迎关注获取最新内容哦~

参考文章

  • JavaScript Visualized: Event Loop