您有沒有想過為什麼某些 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中文網其他相關文章!