JavaScript và những khái niệm cần nắm vững - Phần 2
Ở bài viết đầu tiên, chúng ta đã tìm hiểu một cách sơ lược về JavaScript và cách hoạt động của ngôn ngữ lập trình JavaScript.
Trong bài viết kỳ này, chúng ta sẽ cùng đi sâu hơn về khía cạnh công nghệ để tìm hiểu về tính đơn luồng và xử lý bất đồng bộ của JavaScript.
JavaScript là ngôn ngữ lập trình bất đồng bộ
Đầu tiên, chúng ta cần hiểu được một ngôn ngữ xử lý đồng bộ là như thế nào? Xử lý bất đồng bộ là như thế nào?
Xử lý đồng bộ là xử lý một cách tuần tự, khi bạn chạy một đoạn code, các đoạn code sẽ được xử lý sau khi đoạn code phía trước đã được thực hiện xong.
Xử lý bất đồng bộ là khi bạn chạy một đoạn code, nó không cần chờ đoạn code phía trước phải được xử lý xong và các tiến trình có thể chạy cùng lúc.
Như đã đề cập trên tiêu đề, JavaScript là ngôn ngữ lập trình xử lý bất đồng bộ. Để dễ hiểu hơn, chúng ta hãy cùng tìm hiểu qua ví dụ sau đây:
Chúng ta định nghĩa hàm executionTest1(), bên trong là hàm setTimeout (trình duyệt sẽ chờ 2s) chứa lệnh xuất chuỗi kí tự "This is the execution 1 :)" ra màn hình console. Sau đó là lệnh gọi thực thi hàm executionTest1 và cuối cùng là lệnh in ra chuỗi "This is the execution 2 :)".
Có phải bạn nghĩ rằng kết quả sẽ là như thế này?
Nhưng đây là kết quả cho những gì diễn ra trên thực tế:
Dựa vào kết quả mặc dù chúng ta gọi thực hiện hàm executionTest1() trước, nhưng kết quả lại in ra chuỗi "This is the execution2" trong câu lệnh console.log ở ngoài hàm trước khi in ra chuỗi bên trong hàm.
Vì thế, trong JavaScript câu lệnh phía sau không hoàn toàn phải đợi các lệnh trước đó hoàn thành rồi mới được chạy. Đó chính là xử lý bất đồng bộ. Xử lý bất đồng bộ cũng là một phần đặc trưng của JavaScript khác với phần lớn các ngôn ngữ hiện tại.
JavaScript là ngôn ngữ lập trình đơn luồng
Tại một thời điểm, JavaScript chỉ có thể xử lý một đoạn code đó chính là tính đơn luồng. Cùng với đó, JavaScript Engine chỉ có duy nhất một luồng xử lý và nó gồm: call stack và memory heap.
JavaScript đơn luồng có nghĩa là nó không thể chạy nhiều đoạn code cùng một lúc, vậy thì phải chăng JavaScript sẽ xử lý các đoạn code một cách tuần tự (xử lý đồng bộ)? Thế nhưng ở phía trên, chúng ta đã được biết rằng JavaScript là ngôn ngữ lập trình bất đồng bộ???
Thực ra đó cũng là một đặc trưng quan trọng của JavaScript, mặc dù là một ngôn ngữ lập trình đơn luồng nó vẫn xử lý các tiến trình theo kiểu bất đồng bộ. Vậy thì JavaScript Engine đã thực tế và tối ưu hóa cách xử lý bất đồng bộ này như thế nào?
JavaScript Engine bao gồm 3 thành phần riêng biệt:
1. Call stack
2. Web API
3. Callback Queue
Chúng ta đã biết rằng JavaScript là ngôn ngữ xử lý bất đồng bộ, thế nên lúc hai hàm setTimeout được thực thi, lệnh console log ở cuối cùng được thực thi và các lệnh console log khác cũng sẽ được thực thi sau khi hết thời gian chờ.
Dưới dây là kết quả của đoạn script phía trên:
Bước 1 - JavaScript Engine sẽ xem toàn bộ code từ trên xuống dưới và xác định ra các thành phần trong đoạn code
Bước 2 - Hàm setTimeout thứ nhất sẽ được nạp (fetched) vào Web API và bắt đầu được thực thi.
Bước 3 - Hàm setTimeout thứ hai sẽ được nạp vào Web API và bắt đầu được thực thi.
Bước 4 - Lệnh console.log cuối cùng được nạp vào CallStack và bắt đầu được thực thi. Chính vì lệnh này được chạy ngay lập tức nên chúng ta sẽ thấy output đầu tiên là "This is the execution 1 :)". Sau khi hoàn thành, câu lệnh này sẽ được xóa khỏi CallStack.
Bước 5 - Hàm setTimeout đầu tiên được nạp vào Web API sau khi hết thời gian chờ sẽ được đẩy vào Callback Queue.
Bước 6 - Từ Callback Queue hàm setTimeout đầu tiên được đẩy lên CallStack và chúng ta có thể thấy được output thứ 2 là "This is the execution 2 :)"
Bước 7 - Tương tự, Hàm setTimeout thứ hai sau khi hết thời gian chờ tại Web API cũng sẽ được đẩy vào Callback Queue
Bước 8 - Như bước 6, từ Callback Queue hàm setTimeout thứ hai được đẩy lên CallStack và chúng ta có thể thấy được output cuối cùng là "This is the execution 3 :)"
Nên nhớ rằng, khi một lệnh nào đó đã được thực thi từ CallStack nó sẽ được xóa khỏi CallStack.