7

[译] JavaScript可视化:事件循环

 3 years ago
source link: https://zhuanlan.zhihu.com/p/137276025
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[译] JavaScript可视化:事件循环

Enjoy what you are doing!

事件循环是每一个JavaScript开发者必须以某种方式处理的事情之一,但是起初理解起来可能有点令人困惑。我是一个可视化学习者,所以我认为我应该尝试用一种可视化的方式通过低分辨率的动图(gifs)来帮你解释它。现在是2019年,动图(gifs)还是有一点像素化和模糊。

那么首先,什么是事件循环以及我们为什么应该关心它呢?

JavaScript单线程(single-threaded): 同时只能做一个项目。通常这没什么大不了,但是想象一下,现在你正在运行一个需要30秒的任务。在任务期间,任何其它事情发生(JavaScript默认运行在浏览器的主线程上,因此整个UI是卡住的)之前都需要等待30秒。现在是2019年了,没有人想要一个慢的、响应迟钝的网页。

幸运的是,浏览器给我们一些JavaScript引擎它自己没有提供的特性:WeB API。它包括DOM APIsetTimeoutHTTP请求等内容。这能帮助我们创建一些异步、非阻塞的行为。

当我们调用一个函数的时候,它会被添加到一个叫做调用栈的东西中。调用栈不是浏览器特有的,而是JS引擎的一部分。它是栈意味着它是先进后出(想想一堆煎饼)。当函数返回一个值的时候,它会被弹出栈。

v2-050d10baa1b0a5c6421959dbb21aff9e_b.jpg

图1:当函数被调用的时候会被推入到调用栈中,当函数返回一个值的时候会从调用栈中弹出。

respond函数返回一个setTimeout函数。setTimeout通过Web API提供给我们,它让我们在不阻塞主线程的情况下延迟执行任务。我们为setTimeout传递的回调函数即箭头函数() => { return 'Hey' }被添加到Web API。与此同时,setTimetout函数和respond函数被弹出栈,它们都返回了它们的值。

v2-53e8d25ab9ffbca763742a73d0968885_b.jpg

图2:setTimeout通过浏览器提供,Web API将会注意我们传入的回调

Web API中,定时器运行时间和我们传递给setTimeout的第二个参数一样长,即1000ms。回调函数不会立即被添加到调用栈中,而是会被传给一个叫队列的东西。

v2-bb133167acba9732c254fe1b4e2604a3_b.jpg

图3:当定时器结束的时候(这个例子中是1000ms),回调会被传递到回调队列

这是一个令人疑惑的部分:并不是说在1000ms后,回调函数被添加到调用栈(从而返回一个值)。它是在1000ms后被添加到队列。由于这是一个队列,因此函数必须等到轮到它的时候再执行。

这是我们一直在等待的部分,事件循环去做它唯一任务的时间到了:通过调用栈连接队列!如果调用栈是空的,也就是说所有之前被调用的函数已经返回它们的值并且已经从栈内弹出,那么队列中的第一项会被添加到调用栈。在这个例子中,没有其它的函数被调用,意味着到回调函数在队列中是第一项时调用栈是空的。

v2-7e58f74b2b2116d7cb8ae573570df2a5_b.jpg

图4:事件循环会查看调用队列和调用栈,如果调用栈是空的,它会将队列中的第一项推入栈中。

回调函数被添加到调用栈调用然后返回一个值,最终被弹出栈。

v2-55d383fd555ff564285059d89c8f4abd_b.jpg

图5:回调函数被推入调用占中执行。一但它返回一个值,就会从调用栈内弹出。

阅读文章是有趣的,但是你只有通过不断地实践才能完全理解它。尝试想出如果我们运行如下代码控制台会得到什么?

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

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

明白了吗?让我们快速看一下,在浏览器中运行这段代码时发生了什么?

v2-edca0b28d3de98f77da38d0f994beebd_b.jpg
  1. 我们调用barbar返回一个setTimeout函数。
  2. 我们传递给setTimeout的回调被添加到Web API,setTimeoutbar被弹出调用栈。
  3. 定时器开始运行,与此同时foo被调用并且输出Firstfoo返回undefined,baz被调用,并且回调被添加到队列中。
  4. baz输出Third。在baz返回内容后,事件循环看到调用栈是空的,然后将回调添加到调用栈中。
  5. 回调输出Second

希望这能让你对事件循环更加熟悉!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK