Get in touch
or send us a question?
CONTACT

JavaScript – Tổng quan về engine, runtime & call stack

Tổng quan

Hầu hết mọi người đều đã nghe đến V8 Engine và hầu hết mọi người đều biết Javascript là single-thread hoặc biết rằng Javascript đang sử dụng 1 callback queue.
Trong bài viết này, chúng ta sẽ đào sâu các concepts này và giải thích rõ cách hoạt động của Javascript. Nắm được chi tiết những điều này, bạn sẽ có thể viết được các ứng dụng tốt hơn, không bị block và khai thác các APIs đã cung cấp 1 cách phù hợp.

JavaScript Engine

Ví dụ phổ biến của JavaScript Engine là V8 engine của Google. Ví dụ, V8 engine được sử dụng trong Chrome và Node.js. Đây là tổng quan đơn giản về Javascript Engine.


Engine sẽ gồm 2 components chính:

  • Memory Heap — nơi diễn ra memory allocation
  • Call Stack — nơi các stack frames tồn tại khi code của bạn thực thi

Runtime

Có các APIs trong hệ điều hành được hầu hết các JavaScript developer sử dụng ngoài kia (như “setTimeout”). Tuy nhiên, các API đó không do Engine cung cấp.

Như vậy, chúng ta đã có Engine nhưng thực sự có nhiều thứ khác nữa, như Web APIs được cung cấp bởi các hệ điều hành như DOM, AJAX, setTimeout…
Và sau đó, chúng ta có event loop & callback queue rất phổ biến.

Call Stack

JavaScript là ngôn ngữ lập trình single-threaded, đồng nghĩa là nó có Call Stack single.
Call Stack là 1 data structure ghi lại nơi chúng ta hiện diện trong program. Nếu nói về 1 function, chúng ta đặt nó lên đầu stack. Nếu chúng ta trả về từ 1 function, thì lấy nó ra khỏi phần đầu. Đó chính là tất cả những gì mà stack có thể làm.
Cùng xem ví dụ sau:

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);

Khi engine bắt đầu thực thi đoạn code này, Call Stack sẽ trống. Các bước tiếp theo sẽ như sau:

Mỗi đầu vào trong Call Stack được gọi là Stack Frame

Và đây chính xác là cách mà stack trace được xây dựng khi throw một exception. Về cơ bản thì nó là trạng thái hiện tại của Call Stack khi exception xảy ra. Hãy xem đoạn code dưới đây:

function foo() {
    throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
    foo();
}
function start() {
    bar();
}
start();

Nếu ta chạy đoạn code này trong Chrome thì stack trace sẽ được sinh ra như dưới đây:

Blocking the stack – điều này xảy ra khi bạn vượt quá số stack cho phép. Và điều này rất dễ xảy ra, đặc biệt nếu bạn đang dùng đệ quy mà không check code cẩn thận, như sau:

function foo() {
    foo();
}
foo();

Khi engine bắt đầu thực thi đoạn code này, nó bắt đầu chạy bằng cách gọi hàm foo. Hàm này lại gọi đệ quy đến chính nó mà không có điều kiện nào cả. Bởi vậy, tại mỗi bước thực thi, hàm này lại được thêm vào đỉnh của Call Stack:

Ở một thời điểm nào đó thì số hàm được gọi trong Call Stack sẽ vượt quá kích thước cho phép, và trình duyệt sẽ throw một error, đại loại như này:

Thực thi đoạn mã trên một thread có thể rất đơn giản vì bạn không phải đối mặt với những kịch bản mà môi trường đa luồng (multi-threaded) thường gặp phải, chẳng hạn như deadlocks. Nhưng chạy trên một thread cũng có những giới hạn riêng của nó. Vì JavaScript chỉ có một Call Stack, điều gì sẽ xảy ra khi một việc nào đó xử lý mất nhiều thời gian?

Concurrency & Event Loop

Chuyện gì xảy ra khi bạn có function calls trong Call Stack ngốn rất nhiều thời gian để xử lý? Ví dụ, thử tưởng tượng bạn muốn thực hiện vài chuyển đổi hình ảnh phức tạp trong hệ điều hành.

Vấn đề chính là khi Call Stack có các functions để thực thi, hệ điều hành không thể thực sự làm điều gì khác vì bị block. Đồng nghĩa là hệ điều hành không thể render, không thể chạy code nào khác và bị mắc kẹt. Việc này sẽ gây ra vấn đề nếu bạn muốn có fluid UIs tốt trong ứng dụng của mình.

Thêm nữa, một khi hệ điều hành bắt đầu xử lý quá nhiều tasks trong Call Stack, nó sẽ ngừng responsive trong 1 quãng thời gian khá dài. Và hầu hết các browsers sẽ hành động bằng cách đưa ra error, hỏi bạn là liệu có muốn ngừng web page này không.

Rõ ràng thì đây không phải là một trải nghiệm hoàn hảo cho người dùng rồi, phải không nhỉ?

Vậy làm cách nào để có thể thực thi các đoạn code này mà không chặn UI & khiến cho browser không responsive nữa? Giải pháp là gì mình sẽ trình bày chi tiết ở bài sau.