Event Loop trong JavaScript là một cơ chế cốt lõi giúp ngôn ngữ này quản lý việc thực thi mã, đặc biệt là các tác vụ không đồng bộ, mà không cần tạo ra nhiều luồng (threads) như các ngôn ngữ lập trình khác. Điều này giúp JavaScript duy trì khả năng xử lý sự kiện một cách non-blocking (không bị chặn), cho phép nó tiếp tục thực thi các phần khác của mã trong khi chờ các tác vụ không đồng bộ hoàn thành.

Cách hoạt động của JavaScript:

JavaScript chỉ có một luồng (single-threaded), nghĩa là nó chỉ có thể thực thi một đoạn mã tại một thời điểm trên call stack (ngăn xếp gọi hàm). Tuy nhiên, việc này không có nghĩa là JavaScript không thể xử lý nhiều tác vụ cùng một lúc. Chính event loop là cơ chế chịu trách nhiệm điều phối việc thực thi các tác vụ không đồng bộ như thao tác I/O, timer (setTimeout, setInterval), và event listeners.

Thành phần chính của Event Loop:

  1. Call Stack (Ngăn xếp gọi hàm):
    • Mỗi lần JavaScript thực thi một hàm, hàm đó sẽ được đưa vào call stack.
    • Call stack hoạt động theo cơ chế LIFO (Last In, First Out), nghĩa là hàm cuối cùng được thêm vào sẽ là hàm được thực thi đầu tiên.
    • Nếu một hàm gọi hàm khác, hàm con này sẽ được thêm vào call stack và chỉ được loại bỏ khỏi stack khi đã hoàn thành.
  2. Web APIs (Các API của trình duyệt):
    • Khi gặp một tác vụ không đồng bộ (như gọi AJAX, timers, events), nó sẽ được chuyển cho Web APIs của trình duyệt để xử lý bên ngoài call stack.
    • Ví dụ: Khi sử dụng setTimeout, hàm callback không được thực thi ngay lập tức mà được chuyển đến Web APIs để chờ một khoảng thời gian. Trong thời gian này, call stack vẫn có thể thực hiện các nhiệm vụ khác.
  3. Callback Queue (Hàng đợi callback):
    • Khi các tác vụ không đồng bộ hoàn thành, các hàm callback tương ứng của chúng sẽ được đưa vào callback queue.
    • Các hàm trong callback queue không được thực thi ngay lập tức. Event loop sẽ kiểm tra callback queue và chỉ chuyển các hàm trong đó vào call stack khi call stack rỗng (không còn tác vụ nào khác).
  4. Event Loop (Vòng lặp sự kiện):
    • Event loop liên tục kiểm tra trạng thái của call stack và callback queue.
    • Khi call stack rỗng (nghĩa là không còn hàm nào đang thực thi), event loop sẽ chuyển một hàm từ callback queue vào call stack để thực thi.

Quá trình hoạt động của Event Loop:

  • Khi JavaScript gặp một tác vụ không đồng bộ (ví dụ: setTimeout, thao tác đọc/ghi file, truy vấn database), tác vụ đó sẽ được xử lý bên ngoài call stack và không làm chặn chương trình chính.
  • Sau khi tác vụ hoàn thành (như thời gian chờ của setTimeout hết hạn), hàm callback sẽ được đưa vào callback queue.
  • Event loop kiểm tra nếu call stack trống, nó sẽ lấy hàm callback từ callback queue và đưa vào call stack để thực thi.

Ví dụ chi tiết về Event Loop:

console.log('Start'); // 1

setTimeout(() => {
    console.log('Timeout callback'); // 4 (sau 2 giây)
}, 2000);

console.log('During wait'); // 2

for (let i = 0; i < 1000000000; i++) {
    // Một vòng lặp để "chặn" chương trình, nhưng không ảnh hưởng đến setTimeout
}

console.log('End'); // 3

Phân tích từng bước:

  1. console.log('Start'):
    • Được thêm vào call stack và thực thi ngay lập tức.
    • Output: Start.
  2. setTimeout(...):
    • setTimeout được xử lý bởi Web APIs. Nó không đưa hàm callback vào call stack ngay lập tức, mà chờ 2000ms.
    • Trong thời gian chờ, call stack tiếp tục thực thi các lệnh khác.
  3. console.log('During wait'):
    • Được thêm vào call stack và thực thi ngay lập tức.
    • Output: During wait.
  4. Vòng lặp for lớn:
    • Được thêm vào call stack và xử lý tuần tự. Đây là tác vụ đồng bộ, nên nó sẽ chặn call stack cho đến khi hoàn thành.
    • Trong thời gian này, mặc dù Web APIs đã hết thời gian chờ 2000ms, nhưng callback của setTimeout không thể được thực thi ngay vì call stack vẫn chưa trống.
  5. console.log('End'):
    • Được thêm vào call stack và thực thi ngay sau khi vòng lặp kết thúc.
    • Output: End.
  6. Callback từ setTimeout:
    • Sau khi call stack trống (tức là khi tất cả các hàm đồng bộ đã thực thi xong), event loop sẽ lấy callback từ callback queue (là hàm callback của setTimeout) và đưa nó vào call stack để thực thi.
    • Output: Timeout callback.

Kết quả cuối cùng của đoạn mã trên:

Start
During wait
End
Timeout callback

Kết luận:

  • Event loop trong JavaScript là cơ chế giúp ngôn ngữ này xử lý các tác vụ không đồng bộ một cách hiệu quả mà không làm chặn luồng chính. Nhờ cơ chế này, JavaScript có thể tiếp tục thực thi các tác vụ khác trong khi chờ các tác vụ không đồng bộ hoàn thành, cải thiện hiệu năng của ứng dụng.
  • Call stack chỉ xử lý các tác vụ đồng bộ, trong khi callback queue lưu trữ các callback của các tác vụ không đồng bộ cho đến khi call stack trống.
  • Event loop là cầu nối giữa call stack và callback queue, đảm bảo rằng các tác vụ không đồng bộ chỉ được thực thi khi chương trình không còn tác vụ đồng bộ nào khác.