事件循环!这是每一个JS开发者都会遇到的东西,但一开始理解起来会很复杂。
首先,什么是事件循环,为什么你应该关注它?
JS 是单线程(single-threaded)的:一次只能运行一个任务。通常这没什么大问题,但是现在想象一下,你正在运行一个要耗时30s的任务……我们必须等着这30s过去才能执行其他的代码(JS默认在浏览器的主线程上运行,所以整个UI的解析会卡住。),都2019年了,没有人想要一个缓慢、没有响应的网页。
还好浏览器给了我们一些JS引擎没有提供的特性:Web API。它包括 DOM API,setTimeout
,HTTP requests 等等。它能帮我们创建一些异步的、不会阻塞的行为。
当我们调用一个函数时,它将被添加到一个叫做调用栈的地方。调用栈是JS引擎的一部分,它不是浏览器的所特有的。这是一个堆栈,意味着先进后出(想象一堆煎饼堆叠在一起)。当一个函数返回了一个值时,它就会从调用栈中弹出。
respond
函数返回一个 setTimeout
函数,这个setTimeout
就是 Web API 提供给我们的,它的作用是让我们延迟任务的执行而不是阻塞主线程。我们传给setTimeout
的回调函数:一个箭头函数() => { return 'Hey' }
被加入到 Web API 中。同时,setTimeout
函数和respond
函数从调用栈中弹出,它们都返回了各自的值!
在 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
和bar
都从调用栈中弹出。 3. 定时器运行,与此同时,foo
被调用,输出"First"
。foo
返回undefined
,baz
被调用,然后 Web API 中的回调函数被加入到队列中。 baz
输出"Third"
。在baz
执行完毕后,事件循环发现调用栈是空的,此时队列中的回调函数被添加到调用栈中。- 回调函数输出
"Second"
。
添加我的微信:enjoy_Mr_cat,共同成长,卷卷群里等你 🤪。
以上,感谢您的阅读~