11/08/2018, 20:36

Cách hoạt động của JavaScript (p1): tổng quan về engine, runtime, call stack

Khi JavaScript càng phổ biến hơn, các nhóm phát triển đang tận dụng sự hỗ trợ của nó trên nhiều cấp độ khác nhau trong các dự án front-end, back-end, hybrird apps, thiết bị nhúng, và rất nhiều nữa... Đây là bài đăng đầu tiên trong series nhằm đào sâu vào JavaScript và bản chất cách hoạt động của ...

Khi JavaScript càng phổ biến hơn, các nhóm phát triển đang tận dụng sự hỗ trợ của nó trên nhiều cấp độ khác nhau trong các dự án front-end, back-end, hybrird apps, thiết bị nhúng, và rất nhiều nữa...
Đây là bài đăng đầu tiên trong series nhằm đào sâu vào JavaScript và bản chất cách hoạt động của nó: chúng tôi nghĩ rằng bằng cách hiểu biết về cách xây dựng các blocks của JavaScript và cách thức hoạt động của chúng bạn sẽ có thể viết code và app một cách tốt nhất.

Trên GitHut stats chúng ta thấy rằng, JavaScript nằm trong top đầu về kích hoạt các Repository và Total Pushes trên GitHub.

GitHut

Nếu dự án của bạn dùng hoặc phụ thuộc nhiều vào JavaScript, điều này có nghĩa: các nhà phát triển phải sử dụng nhiều các ngôn ngữ, các hệ sinh thái kèm theo phải hiểu sâu hơn về nó để phát triển một dự án tốt hơn.

Hằng ngày, có rất nhiều nhà phát triển đang sử dụng JavaScript nhưng họ không hiểu cấu trúc bên trong của nó.

Tổng Quan

Hầu hết mọi người đã nghe khái niệm "V8 Engine" và hầu hết mọi người đã biết JavaScript là Single-thread (đơn luồng ) hoặc nó đang sử dụng Call back queue.

Trong bài này, tôi sẽ giải thích và đi sâu vào tất cả các khái niệm này và giải thích hoạt động của JavaScript. Bằng cách hiểu rõ chi tiết các vấn đề này, bạn có thể viết các ứng dụng non-blocking tốt hơn sử dụng đúng các API được cung cấp.

Nếu bạn chỉ mới biết về JavaScript. bài viết nàu sẽ giúp bạn hiểu rằng tại sao JavaScript rất "lạ" so với các ngôn ngữ khác.

Và nếu bạn đã là lập trình viên JavaScript có kinh nghiệm, hy vọng nó sẽ cho bạn một số hiểu biết mới về JavaScript Runtime mà bạn đang sử dụng hằng ngày thực sự nó hoạt động thế nào.

JavaScript Engine

Một ví dụ rất phổ biến của JavaScript Engine là Google's V8 engine. V8 Engine được sử dụng bên trong ChromeNode.js. Dưới đây là một cái nhìn rất đơn giản bên trong một engine:
engine

Engine bao gồm 2 thành phần chính:

  • Memory Heap - Đây là nơi cấp phát bộ nhớ.
  • Call Stack - Đây là nơi chứa các khung stack khi các đoạn mã của bạn thực thi.

Runtime

Có các API bên trong trình duyệt đã được sử dụng bởi hầu hết các lập trình viên JavaScript sử dụng (ví dụ: "setTimeout"). Tuy nhiên, những API đó không được cung cấp bởi Engine.

Vậy chúng từ đâu mà có?

Thực ra nó phức tạp hơn những gì chúng ta nghĩ.
alt text

Vì vậy, chúng ta có Engine nhưng thực sự lại có nhiều hơn thế nữa. Chúng ta có những thứ gọi là các Web API được cung cấp bởi các trình duyệt như DOM, AJAX, setTimeout và rất nhiều nữa.

Call Stack

JavaScript là một ngôn ngữ "đơn luồng" , có nghĩa là chỉ có một Call Stack duy nhất. Vì thế nó có thể làm một điều tại một thời điểm.

Call Stack là một cấu trúc dữ liệu với các bản ghi cơ bản ghi lại những chương trình chúng ta có.

Nếu chúng ta bước vào một function, chúng ta sẽ đặt nó trên đỉnh của stack. Nếu chúng ta return từ một function, chúng ta bật nó lên trên stack. Đó là tất cả các stack có thể làm.

Hãy xem một ví dụ: Hãy xem đoạn code 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 các đoạn mã, Call Stack sẽ trống. Sau đó, các bước sẽ thực hiện như sau:

call-stack

Mỗi mục bên trong Call Stack được gọi là các Stack Frame(khung Stack).

Và chính xác là các stack được xây dựng như thế nào khi có một ngoại lệ được ném ra - nó là một trạng thái cơ bản của Call Stack khi ngoại lệ xảy ra. Hãy nhìn những đoạn mã sau đây:

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

Nếu nó được thực thi bởi Chrome (giả định rằng đoạn code ở trong một file có tên là foo.js) các stack sẽ được tạo ra:
alt text

"Blowing the stack" - đây là điều xảy ra khi bạn chạm tới kích thước tối đa của Call Stack. Và điều đó có thể xảy ra khá dễ dàng, đặc biệt nếu bạn sử dụng "đệ quy" mà không cần phải có một đoạn mã quá dài để kiểm tra vấn đề đó. Hãy xem đoạn mã dưới đây:

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

Khi engine bắt đầu thực thi đoạn mã này, nó bắt đầu gọi function "foo". Tuy nhiên function này là đệ quy và nó bắt đầu gọi chính nó mà không có điều kiện để kết thúc. Vì thế tại mỗi bước mà engine thực thi, các function giống nhau được thêm vào Call Stack nhiều lần. Nó trông như thế này:
Blowing-stack

Tuy nhiên một số thời điểm, số lượng function được gọi bên trong Call Stack vượt quá kích thước thực tế của Call Stack, và trình duyệt sẽ quyết định hành động bằng cách ném ra một lỗi, có thể giống như thế này:
throw-call-stack

Chạy các đoạn mã trên single thread đơn giản có thể khá dễ dàng vì bạn không phải đối mặt với các kịch bản phức tạp trong các môi trường multi-thread (đa luồng) ví dụ như các deadlock.

Nhưng chạy trên single-thread cũng khá hạn chế. Điều gì xảy ra khi mọi thứ diễn ra chậm?

Concurrency & Event Loop

Cái gì xảy ra khi bạn gọi function trong Call Stack cần một lượng lớn thời gian để xử lý? Ví dụ: hãy tưởng tượng rằng bạn muốn thực hiện một số chuyển đổi hình ảnh phức tạp với JavaScript trong trình duyệt.

Bạn có thể hỏi - tại sao đó cũng là một vấn đề? Vấn đề là trong khi Call Stack có function đang thực thi, thì trình duyệt có thể không thực sự làm bất cứ điều gì khác - nó đang bị chặn. Điều này có nghĩa là trình duyệt không thể render, nó không chạy bất kì đoạn mã nào, nó đang mắc kẹt ở chỗ nào đấy!!! Và điều này đã tạo ra một vấn đề trong lúc bain muốn tạo nên một giao diện đẹp trong ứng dụng của bạn.

Và điều đó không phải là vấn đề duy nhất. Một khi trình duyệt của bạn bắt đầu xử lý quá nhiều task trong Call Stack, nó sẽ ngừng đáp ứng trong một thời gian dài. Và hầu hết trình duyệt sẽ hành động bằng cách đưa ra lỗi, hỏi bạn có muốn ngừng trang web hay không.
alt text

Vậy, bây giờ điều đó không phải là trải nghiệm tốt nhất cho người dùng, đúng không?

Vậy, làm thế nào để chúng ta thực thi được những đoạn mã nặng mà không ảnh hưởng đến giao diện của người dùng, và trình duyệt vẫn hoạt động bình thường. Tốt thôi, giải pháp đưa ra là asynchronous callbacks.

Điều này sẽ giải thích chi tiết hơn trong phần 2 của series "How JavaScript actually works" : “Inside the V8 engine + 5 tips on how to write optimized code”.

Bài viết được dịch, và tham khảo từ http://bit.ly/2IFIpbj

HaoPhan 10-04-2018

0