JavaScript可视化:事件循环

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

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

当我们调用一个函数的时候,它会被添加到一个叫做调用栈的东西中。调用栈不是浏览器特有的,而是JS引擎的一部分。它是栈意味着它是先进后出(想想一堆煎饼)。当函数返回一个值的时候,它会被弹出栈。
JavaScript可视化:事件循环_第1张图片
图1:当函数被调用的时候会被推入到调用栈中,当函数返回一个值的时候会从调用栈中弹出

respond函数返回一个setTimeout函数。setTimeout通过Web API提供给我们,它让我们在不阻塞主线程的情况下延迟执行任务。我们为setTimeout传递的回调函数即箭头函数() => { return ‘Hey’ }被添加到Web API。与此同时,setTimetout函数和respond函数被弹出栈,它们都返回了它们的值。
JavaScript可视化:事件循环_第2张图片
图2:setTimeout通过浏览器提供,Web API将会注意我们传入的回调

在Web API中,定时器运行时间和我们传递给setTimeout的第二个参数一样长,即1000ms。回调函数不会立即被添加到调用栈中,而是会被传给一个叫队列的东西。
JavaScript可视化:事件循环_第3张图片
图3:当定时器结束的时候(这个例子中是1000ms),回调会被传递到回调队列

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

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

回调函数被添加到调用栈调用然后返回一个值,最终被弹出栈。
JavaScript可视化:事件循环_第5张图片
图5:回调函数被推入调用占中执行。一但它返回一个值,就会从调用栈内弹出。

尝试想出如果我们运行如下代码控制台会得到什么?

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

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

来解析下这个过程
JavaScript可视化:事件循环_第6张图片

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

你可能感兴趣的:(javascript)