作者 | Lydia Hallie
来源 | dev.to,JavaScript Visualized: Event Loop
事件循环是每个 Javascript 开发人员都必须面对的,起初理解起来会有些混乱。我是一个视觉方向的工程师,所以我将尝试以 gif 图像来更直观地帮助你理解它。
首先,事件循环是什么,为什么我们需要关注它?
我们都知道 Javascript 是单线程的:一次只能运行一个任务。通常,这没什么大问题,但是假设你现在正在运行一个耗时需要 30s 的任务。由于是单线程处理,我们需要等待 30s 才能去做其它事情(默认情况下,Javascript 在浏览器的主线程上运行,因此整个界面将卡住)。我想,现在没有人想访问这样一个反应迟钝的网站了。
幸运的是,浏览器为我们提供了一个 Javascript 引擎本身没有的功能:Web API。它包含了 DOM API、setTimeout、HTTP 请求 等等。这可帮助我们创建一些异步的,非阻塞的行为。
当我们调用一个函数时,函数会被添加到调用栈中。调用栈是 JS 引擎的一部分,而不是与游览器相关的。它是一个栈,即满足 FIFO。当一个函数返回一个值时,会从栈中弹出。
response
函数返回一个 setTimeout
函数。 setTimeout
函数是由 Web API 提供的:它让我们可以延迟任务而不会阻塞主线程。我们传递给 setTimeout
函数的回调函数,即箭头函数 () => { return 'Hey'}
会被添加到 Web API。同时, setTimeout
函数和 response
函数从栈中弹出,它们都返回了自己的值。
在 Web API 中,计时器运行了 1000ms (我们传递的第二个参数)。回调不会立即添加到调用栈中,而是会传递到队列中。
这可能是一个令人困惑的地方:这并不意味着在 1000ms 后将回调函数添加到调用栈中(从而返回一个值)。而只是简单地在 1000ms 后添加到队列中。而在队列中,函数必须等待,直到轮到它!
现在到了我们要讨论的重点:事件循环。事件循环的唯一任务是将队列与调用栈连接起来。如果调用栈为空,也就是先前调用的函数都返回了值并从调用栈中调出,这时队列中的第一项便可以添加到调用栈中。
回调被添加到调用栈中,被调用,并返回一个值,然后从调用栈中弹出。
我们再举个例子。运行下面的命令,看看会输出什么内容
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");
bar();
foo();
baz();
看出来了么?让我们看看在浏览器中运行这段代码会发生什么。
- 首先调用
bar
,bar
返回一个setTimeout
函数。 - 我们传递给
setTimeout
的回调被添加到 Web API,setTimeout
函数从调用栈中弹出 - 运行计时器,同时调用
foo
并打印First
。foo
返回,调用baz
,回调函数被添加到队列。 baz
打印出Third
,事件循环发现在baz
返回后,调用栈为空,所以将回调添加到调用栈中。- 回调打印出
Second
希望这篇文章能让你对事件循环有个更好的了解。