JavaScript - ngôn ngữ lập trình phổ biến đứng sau gần 90% website trên thế giới, lại ẩn chứa một khái niệm phức tạp và thường bị hiểu sai: vòng lặp sự kiện (event loop). Bài viết này sẽ giải thích dễ hiểu về vòng lặp sự kiện, hàng đợi tác vụ, ngăn xếp cuộc gọi, hàng đợi tác vụ vi mô và Web API.
1. Web API là gì?
Là công cụ bổ sung được cung cấp bởi trình duyệt hoặc Node.js, Web API xử lý các tác vụ như tạo yêu cầu mạng (sử dụng fetch), đặt hẹn giờ (setTimeout) hoặc truy cập vị trí người dùng (sử dụng Geolocation API). Các tác vụ này hoạt động độc lập bên ngoài luồng JavaScript chính. Ví dụ, đoạn mã sau sẽ hiển thị dòng chữ "Timer done!" sau 2 giây, trong khi đó, luồng JavaScript chính vẫn tiếp tục chạy các đoạn mã khác.
setTimeout(() => { console.log("Timer done!");
}, 2000);
2. Hàng đợi tác vụ là gì?
Hàng đợi tác vụ (Task Queue) hoạt động như một "phòng chờ", nơi các hàm callback từ Web API, event listener và các hành động trì hoãn khác kiên nhẫn chờ đợi đến lượt được JavaScript xử lý. Hãy tưởng tượng hàng đợi tác vụ như một hàng người xếp hàng chờ tại cửa hàng. Mỗi tác vụ sẽ được xử lý bởi vòng lặp sự kiện khi JavaScript hoàn thành tác vụ hiện tại.
3. Call Stack là gì?
Ngược lại, ngăn xếp cuộc gọi (Call Stack) là nơi JavaScript theo dõi các lời gọi hàm. Khi một hàm được gọi, nó sẽ được đưa vào ngăn xếp. Khi hàm hoàn thành, nó sẽ bị loại bỏ khỏi ngăn xếp. JavaScript xử lý các tác vụ theo thứ tự chúng xuất hiện trong ngăn xếp, thể hiện tính chất đồng bộ của nó.
4. Event Loop là gì?
Vòng lặp sự kiện (Event Loop) đóng vai trò như một "người điều phối giao thông", đảm bảo mọi thứ diễn ra suôn sẻ. Nó liên tục kiểm tra xem ngăn xếp cuộc gọi có trống hay không. Nếu trống, nó sẽ di chuyển các tác vụ từ hàng đợi tác vụ hoặc hàng đợi tác vụ vi mô vào ngăn xếp để thực thi. Cơ chế này cho phép JavaScript xử lý mã bất đồng bộ mà không làm gián đoạn luồng chính.
Để hiểu rõ hơn về cách thức hoạt động của vòng lặp sự kiện, hãy cùng phân tích đoạn mã sau:
setTimeout(() => { console.log("2000ms");
}, 2000); setTimeout(() => { console.log("100ms");
}, 100); console.log("End");
Đầu tiên, "End" được in ra ngay lập tức vì nó là mã đồng bộ và được chạy trong ngăn xếp cuộc gọi. Sau đó, setTimeout với 100ms được xử lý bởi Web API. Sau 100ms, hàm callback của nó được chuyển đến hàng đợi tác vụ. Tương tự, setTimeout với 2000ms cũng được xử lý bởi Web API và hàm callback của nó được chuyển đến hàng đợi tác vụ sau 2000ms. Vòng lặp sự kiện sẽ di chuyển hàm callback 100ms vào ngăn xếp cuộc gọi trước, sau đó là hàm callback 2000ms.
5. Microtask Queue là gì?
Hàng đợi tác vụ vi mô (Microtask Queue) là một hàng đợi đặc biệt dành cho các tác vụ được xử lý trước hàng đợi tác vụ. Các tác vụ vi mô xuất phát từ các đối tượng như Promises hoặc mutation observer. Vòng lặp sự kiện luôn kiểm tra hàng đợi tác vụ vi mô trước hàng đợi tác vụ.
Ví dụ sau minh họa cho cách thức hoạt động của hàng đợi tác vụ vi mô với Promise:
console.log("Start"); setTimeout(() => { console.log("Timeout");
}, 0); Promise.resolve().then(() => { console.log("Promise");
}); console.log("End");
Đầu tiên, "Start" được in ra. Sau đó, hàm callback của setTimeout được đưa vào hàng đợi tác vụ. Tiếp theo, kết quả của Promise được đưa vào hàng đợi tác vụ vi mô. "End" được in ra. Vòng lặp sự kiện kiểm tra hàng đợi tác vụ vi mô và thực thi hàm callback của Promise. Cuối cùng, hàng đợi tác vụ xử lý hàm callback của setTimeout.
Tóm lại, Web API xử lý các tác vụ bất đồng bộ bên ngoài luồng chính. Vòng lặp sự kiện di chuyển các tác vụ từ hàng đợi tác vụ hoặc hàng đợi tác vụ vi mô vào ngăn xếp cuộc gọi. Các tác vụ vi mô được xử lý trước các tác vụ trong hàng đợi tác vụ. Hiểu rõ cơ chế hoạt động của vòng lặp sự kiện là chìa khóa để làm chủ JavaScript và xây dựng các ứng dụng web hiệu quả, mượt mà.