测验📣:您对异步 JavaScript 的理解程度如何?

2025-05-25

测验📣:您对异步 JavaScript 的理解程度如何?

在过去的几周里,我们就异步 JavaScript 以及我们在项目中构建高性能应用的模式进行了大量讨论。最终形成了一篇文章——编写更好的 async/await 代码的 4 个技巧除了异步编码模式和最佳实践等实际方面之外,讨论的主题之一是了解 JavaScript 如何在底层处理异步代码的重要性。

异步代码会被传递到其中一个队列中等待,并在调用堆栈为空时执行。队列和调用堆栈中的任务由事件循环协调——这是 JavaScript 避免阻塞主线程的关键机制。点击此处了解更多信息。

我们收集了 4 个有趣的代码示例(看起来 4 是我们最喜欢的数字 😉),它们将帮助你测试你对事件循环和 JavaScript 异步执行流程的理解。让我们开始吧 ⏬

1.哪个队列先执行?

在深入研究事件循环、调用堆栈和任务之前,让我们先来问一个小热身问题。

并非所有队列都生来平等。假设setTimeout()回调会被推送到任务队列,而then()回调会被推送到微任务队列,你认为哪个会先记录日志?

// Task queue 
setTimeout(() => console.log('timeout'), 0)

// Microtask queue 
Promise.resolve().then(() => console.log('promise'))
Enter fullscreen mode Exit fullscreen mode

显示答案👇
promise 
timeout
Enter fullscreen mode Exit fullscreen mode

任务队列中安排的任务将首先运行。但是等等,为什么回调函数的输出setTimeout()在我们的示例中排在第二位?

在每次迭代中,事件循环将首先运行任务队列中最早存在的任务,然后运行微任务队列中的所有微任务。当事件循环开始其第一次迭代时,任务队列仅包含一个任务 - 主程序脚本的运行。回调setTimeout()在第一次迭代期间添加到任务队列中,并且仅在下一次迭代期间从任务队列中排队。

为了更好地理解这些令人兴奋的概念,请查看Jake Archibald 制作的这个动画图表。


2. 下面代码的输出是什么?

要回答这个问题,您需要熟悉同步与异步代码执行顺序以及事件循环如何运行任务等概念。

同样重要的是,你还需要知道哪些代码是同步运行的,哪些是异步运行的。提示:并非所有与 Promise 相关的代码都是异步的。🤯

下面有四个console.log()调用。控制台中会打印什么?打印顺序是怎样的?

let a = 1

setTimeout(() => {
    console.log(a) //A
    a = 2
}, 0)

const p = new Promise(resolve => {
    console.log(a) // B
    a = 3
    resolve()
})

p.then(() => console.log(a)) // C

console.log(a) // D
Enter fullscreen mode Exit fullscreen mode

显示答案👇
/* B */ 1
/* D */ 3
/* C */ 3
/* A */ 3
Enter fullscreen mode Exit fullscreen mode

执行器函数中的代码new Promise在 Promise 进入已解析状态(resolve()调用 时)之前同步运行。因此,示例代码会记录日志1并将变量a值设置为3

console.log()在所有进一步的调用中,变量值保持不变。


3. 信件将按照什么顺序记录?

DOM 事件如何融入事件循环任务处理机制?这里我们有一个div包含button元素的容器。事件监听器被添加到按钮和容器中。由于点击事件会冒泡,因此两个监听器处理程序都会在按钮点击时执行。

<div id="container">
  <button id="button">Click</button>
</div>
Enter fullscreen mode Exit fullscreen mode

点击按钮后输出是什么?

const 
  container = document.getElementById('container'),
  button = document.getElementById('button')

button.addEventListener('click', () => {
  Promise.resolve().then(() => console.log('A'))
  console.log('B')
})

container.addEventListener('click', () => console.log('C'))
Enter fullscreen mode Exit fullscreen mode

显示答案👇
B
A
C
Enter fullscreen mode Exit fullscreen mode

这并不奇怪。调度事件和执行处理程序的任务click将通过事件循环调用,首先记录同步代码,然后then()记录回调。接下来,事件冒泡并执行容器事件处理程序。


4.产量会改变吗?

代码和上一个示例一样,只是button.click()在末尾加了一点。这是一个奇怪的 UI 设计模式,按钮会自动被点击。你觉得这会改变游戏规则吗?还是日志记录顺序保持不变?🤔

const 
  container = document.getElementById('container'),
  button = document.getElementById('button')

button.addEventListener('click', () => {
  Promise.resolve().then(() => console.log('A'))
  console.log('B')
})

container.addEventListener('click', () => console.log('C'))

button.click()
Enter fullscreen mode Exit fullscreen mode

显示答案👇
B
C
A
Enter fullscreen mode Exit fullscreen mode

字符串的记录顺序确实不同。button.click()这造成了很大的差异,它位于调用栈的底部,阻止了微任务队列任务的执行。只有在调用栈清空后,() => console.log('A')才会从微任务队列中取出。


欢迎在评论区✍️分享你那些精彩绝伦的异步和事件循环相关代码示例。别忘了点赞❤️并关注我们,获取更多 Web 开发内容。

文章来源:https://dev.to/ditdot/quiz-how-well-do-you-understand-asynchronous-javascript-5e4j
PREV
如何使用 React 和 Socket.io 构建实时群聊应用程序
NEXT
像专业人士一样编写代码的 4 大开源 AI 🧠 🤖