作者 | Lydia Hallie
    来源 | dev.to,JavaScript Visualized: Event Loop

    事件循环是每个 Javascript 开发人员都必须面对的,起初理解起来会有些混乱。我是一个视觉方向的工程师,所以我将尝试以 gif 图像来更直观地帮助你理解它。

    首先,事件循环是什么,为什么我们需要关注它?

    我们都知道 Javascript 是单线程的:一次只能运行一个任务。通常,这没什么大问题,但是假设你现在正在运行一个耗时需要 30s 的任务。由于是单线程处理,我们需要等待 30s 才能去做其它事情(默认情况下,Javascript 在浏览器的主线程上运行,因此整个界面将卡住)。我想,现在没有人想访问这样一个反应迟钝的网站了。

    幸运的是,浏览器为我们提供了一个 Javascript 引擎本身没有的功能:Web API。它包含了 DOM API、setTimeout、HTTP 请求 等等。这可帮助我们创建一些异步的,非阻塞的行为。

    当我们调用一个函数时,函数会被添加到调用栈中。调用栈是 JS 引擎的一部分,而不是与游览器相关的。它是一个栈,即满足 FIFO。当一个函数返回一个值时,会从栈中弹出。

    gid1.6.gif

    response 函数返回一个 setTimeout 函数。 setTimeout 函数是由 Web API 提供的:它让我们可以延迟任务而不会阻塞主线程。我们传递给 setTimeout 函数的回调函数,即箭头函数 () => { return 'Hey'} 会被添加到 Web API。同时, setTimeout 函数和 response 函数从栈中弹出,它们都返回了自己的值。

    gif2.1.gif

    在 Web API 中,计时器运行了 1000ms (我们传递的第二个参数)。回调不会立即添加到调用栈中,而是会传递到队列中。

    gif3.1.gif

    这可能是一个令人困惑的地方:这并不意味着在 1000ms 后将回调函数添加到调用栈中(从而返回一个值)。而只是简单地在 1000ms 后添加到队列中。而在队列中,函数必须等待,直到轮到它!

    现在到了我们要讨论的重点:事件循环。事件循环的唯一任务是将队列与调用栈连接起来。如果调用栈为空,也就是先前调用的函数都返回了值并从调用栈中调出,这时队列中的第一项便可以添加到调用栈中。

    gif4.gif

    回调被添加到调用栈中,被调用,并返回一个值,然后从调用栈中弹出。

    gif5.gif

    我们再举个例子。运行下面的命令,看看会输出什么内容

    1. const foo = () => console.log("First");
    2. const bar = () => setTimeout(() => console.log("Second"), 500);
    3. const baz = () => console.log("Third");
    4. bar();
    5. foo();
    6. baz();

    看出来了么?让我们看看在浏览器中运行这段代码会发生什么。

    gif14.1.gif

    • 首先调用 barbar 返回一个 setTimeout 函数。
    • 我们传递给 setTimeout 的回调被添加到 Web API, setTimeout 函数从调用栈中弹出
    • 运行计时器,同时调用 foo 并打印 Firstfoo 返回,调用 baz ,回调函数被添加到队列。
    • baz 打印出 Third ,事件循环发现在 baz 返回后,调用栈为空,所以将回调添加到调用栈中。
    • 回调打印出 Second

    希望这篇文章能让你对事件循环有个更好的了解。