您有没有想过为什么某些 JavaScript 代码似乎运行不正常?理解这一点的关键是事件循环。
JavaScript 的事件循环可能很难理解,尤其是在处理不同类型的异步操作时。在本文中,我们将详细介绍 JavaScript 如何处理同步和异步代码、微任务和宏任务,以及为什么会发生某些事情按特定顺序。
JavaScript 以两种主要方式处理操作:同步 和 异步。理解它们之间的区别是掌握 JavaScript 如何处理任务以及如何编写高效、非阻塞代码的关键。
JavaScript 中默认使用同步代码,这意味着每一行都按顺序依次运行。例如:
console.log("First"); console.log("Second");
这将输出:
First Second
另一方面,异步代码允许某些任务在后台运行并稍后完成,而不会阻塞其余代码。像 setTimeout() 或 Promise 这样的函数就是异步代码的示例。
这是使用 setTimeout() 的异步代码的简单示例:
console.log("First"); setTimeout(() => { console.log("Second"); }, 0); console.log("Third");
这将输出:
First Third Second
JavaScript 中有几种处理异步操作的方法:
代码示例:
console.log("Start"); function asyncTask(callback) { setTimeout(() => { console.log("Async task completed"); callback(); }, 2000); } asyncTask(() => { console.log("Task finished"); }); console.log("End");
代码示例:
console.log("Start"); const asyncTask = new Promise((resolve) => { setTimeout(() => { console.log("Async task completed"); resolve(); }, 2000); }); asyncTask.then(() => { console.log("Task finished"); }); console.log("End");
代码示例:
console.log("Start"); async function asyncTask() { await new Promise((resolve) => { setTimeout(() => { console.log("Async task completed"); resolve(); }, 2000); }); console.log("Task finished"); } asyncTask(); console.log("End");
为了更好地理解 javascript 的每种执行方法以及它们之间的差异,这里详细介绍了 javascript 函数的多个方面的差异。
Aspect | Synchronous Code | Asynchronous Code |
---|---|---|
Execution Order | Executes line by line in a sequential manner | Allows tasks to run in the background while other code continues to execute |
Performance | Can lead to performance issues if long-running tasks are involved | Better performance for I/O-bound operations; prevents UI freezing in browser environments |
Code Complexity | Generally simpler and easier to read | Can be more complex, especially with nested callbacks (callback hell) |
Memory Usage | May use more memory if waiting for long operations | Generally more memory-efficient for long-running tasks |
Scalability | Less scalable for applications with many concurrent operations | More scalable, especially for applications handling multiple simultaneous operations |
This comparison highlights the key differences between synchronous and asynchronous code, helping developers choose the appropriate approach based on their specific use case and performance requirements.
In JavaScript, microtasks and macrotasks are two types of tasks that are queued and executed in different parts of the event loop, which determines how JavaScript handles asynchronous operations.
Microtasks and macrotasks are both queued and executed in the event loop, but they have different priorities and execution contexts. Microtasks are processed continuously until the microtask queue is empty before moving on to the next task in the macrotask queue. Macrotasks, on the other hand, are executed after the microtask queue has been emptied and before the next event loop cycle starts.
Microtasks are tasks that need to be executed after the current operation completes but before the next event loop cycle starts. Microtasks get priority over macrotasks and are processed continuously until the microtask queue is empty before moving on to the next task in the macrotask queue.
console.log("Start"); Promise.resolve().then(() => { console.log("Microtask"); }); console.log("End");
Start End Microtask
Macrotasks are tasks that are executed after the microtask queue has been emptied and before the next event loop cycle starts. These tasks represent operations like I/O or rendering and are usually scheduled after a certain event or after a delay.
console.log("Start"); setTimeout(() => { console.log("Macrotask"); }, 0); console.log("End");
Start End Macrotask
Aspect | Microtasks | Macrotasks |
---|---|---|
Execution Timing | Executed immediately after the current script, before rendering | Executed in the next event loop iteration |
Queue Priority | Higher priority, processed before macrotasks | Lower priority, processed after all microtasks are complete |
Examples | Promises, queueMicrotask(), MutationObserver | setTimeout(), setInterval(), I/O operations, UI rendering |
Use Case | For tasks that need to be executed as soon as possible without yielding to the event loop | For tasks that can be deferred or don't require immediate execution |
事件循环是 JavaScript 中的一个基本概念,尽管 JavaScript 是单线程的,但它仍然可以实现非阻塞异步操作。它负责处理异步回调并确保 JavaScript 继续平稳运行,而不会被耗时的操作阻塞。
事件循环是一种允许 JavaScript 高效处理异步操作的机制。它不断检查调用堆栈和任务队列(或微任务队列)以确定接下来应该执行哪个函数。
为了更好地理解事件循环,了解 JavaScript 内部的工作原理非常重要。值得注意的是,JavaScript 是一种单线程语言,这意味着它一次只能做一件事。只有一个调用堆栈,它存储要执行的函数。这使得同步代码变得简单,但它给从服务器获取数据或设置超时等需要时间才能完成的任务带来了问题。如果没有事件循环,JavaScript 将陷入等待这些任务的状态,并且不会发生其他任何事情。
调用堆栈是保存当前正在执行的函数的地方。 JavaScript 在处理代码时会在调用堆栈中添加和删除函数。
当遇到像 setTimeout、fetch 或 Promise 这样的异步任务时,JavaScript 会将该任务委托给浏览器的 Web API(如 Timer API、Network API 等),后者在后台处理该任务。
一旦异步任务完成(例如,计时器完成,或者从服务器接收到数据),回调(处理结果的函数)就会被移动到任务队列(或者在 Promise 的情况下为微任务队列) .
JavaScript 继续执行同步代码。一旦调用堆栈为空,事件循环就会从任务队列(或微任务队列)中取出第一个任务并将其放入调用堆栈中执行。
这个过程会重复。事件循环确保当前同步任务完成后处理所有异步任务。
现在我们对事件循环的工作原理有了更好、更清晰的了解,让我们看一些示例来巩固我们的理解。
function exampleOne() { console.log("Start"); setTimeout(() => { console.log("Timeout done"); }, 1000); Promise.resolve().then(() => { console.log("Resolved"); }); console.log("End"); } exampleOne();
Start End Resolved Timeout done
function exampleTwo() { console.log("Start"); setTimeout(() => { console.log("Timer 1"); }, 0); Promise.resolve().then(() => { console.log("Promise 1 Resolved"); setTimeout(() => { console.log("Timer 2"); }, 0); return Promise.resolve().then(() => { console.log("Promise 2 Resolved"); }); }); console.log("End"); } exampleTwo();
Start End Promise 1 Resolved Promise 2 Resolved Timer 1 Timer 2
function exampleThree() { console.log("Step 1: Synchronous"); setTimeout(() => { console.log("Step 2: Timeout 1"); }, 0); Promise.resolve().then(() => { console.log("Step 3: Promise 1 Resolved"); Promise.resolve().then(() => { console.log("Step 4: Promise 2 Resolved"); }); setTimeout(() => { console.log("Step 5: Timeout 2"); }, 0); }); setTimeout(() => { console.log( "Step 6: Immediate (using setTimeout with 0 delay as fallback)" ); }, 0); console.log("Step 7: Synchronous End"); } exampleThree();
Step 1: Synchronous Step 7: Synchronous End Step 3: Promise 1 Resolved Step 4: Promise 2 Resolved Step 2: Timeout 1 Step 6: Immediate (using setTimeout with 0 delay as fallback) Step 5: Timeout 2
In JavaScript, mastering synchronous and asynchronous operations, as well as understanding the event loop and how it handles tasks, is crucial for writing efficient and performant applications.
The examples provided progressively illustrated the interaction between synchronous code, promises, timers, and the event loop. Understanding these concepts is key to mastering asynchronous programming in JavaScript, ensuring your code runs efficiently and avoids common pitfalls such as race conditions or unexpected execution orders.
To ensure you don't miss any part of this series and to connect with me for more in-depth discussions on Software Development (Web, Server, Mobile or Scraping / Automation), push notifications, and other exciting tech topics, follow me on:
Stay tuned and happy coding ???
以上是了解 JavaScript 中的异步编程:事件循环初学者指南的详细内容。更多信息请关注PHP中文网其他相关文章!