Hiểu JavaScript như trình duyệt web
Để nối tiếp cho phần trước "Trình duyệt web render HTML và CSS như thế nào?" và hiểu được toàn bộ quá trình hiển thị trang web của bạn, chúng ta hãy cùng đến với cách mà Trình duyệt web dùng để "thấu hiểu" được một ngôn ngữ cũng thuộc hàng "khó đoán" như JavaScript.
Nếu bạn đã bỏ lỡ phần đầu tiên, không sao! Bạn có thể xem lại ở đây:
Trình duyệt web render HTML và CSS như thế nào?Chúng ta hãy cùng sẵn sàng để bắt đầu chuyến hành trình tìm hiểu cách mà trình duyệt của bạn xử lý và biểu diễn một webpage nhé! |
Khi bạn truy cập một trang web bằng bất kỳ trình duyệt nào, trình duyệt của bạn đều thực hiện HTTP GET request để tải trang HTML bao gồm cả thẻ <script> và đưa ra một request riêng để tải tập lệnh này.
Trình duyệt nhận được mã JavarScipt được rút gọn từ server, JavaScript Engine sẽ bắt đầu công việc của mình ở đây, tại giai đoạn này.
Các trình duyệt khác nhau sẽ có các JavaScript Engine khác nhau dùng để giải mã code JavaScript. Trong nội dung bài viết này, chúng ta chỉ tập trung vào Engine V8, Engine được sử dụng trong trình duyệt Google Chrome và đồng thời cũng là Engine của Nodejs.
Dưới đây là tổng quan về các bước mà chúng ta phải trải qua khi thực thi JavaScript.
Còn bây giờ, chúng ta cùng tiến hành từng bước để hiểu rõ hơn điều gì đang xảy ra nhé!
Mã hóa - Tokenization
Trong bước đầu tiên này, Engine chuyển đổi các dòng code thành một mảng mã thông báo (tokens), Engine duyệt qua từng ký tự để tiên hành phân loại.
Ví dụ, nếu bạn có một dòng mã như thế này let x = 10; Engine sẽ chuyển đổi nó thành một loạt các mã thông báo như thế này
Sau đó Engine sẽ sử dụng bảng mã này để xây dựng một "Cây cú pháp trừu tượng" (the Abstract Syntax Tree (AST))
Phân tích cú pháp - Parsing
Engine V8 bắt đầu chuyển đổi mảng mã thông báo này thành Cây cú pháp trừu tượng, cấu trúc dữ liệu này chính là đại diện cho đoạn code JavaScript của chúng ta. Một trong những điều quan trọng nhất đối với Engine trong bước phân tích cú pháp này là việc xác định phạm vi của mỗi biến.
Ví dụ, nếu chúng ta có một hàm như thế này:
Engine sẽ chuyển đổi nó thành AST như thế này:
Bạn có thể xem AST của bất kỳ đoạn code JavaScript nào đó trong Engine V8 bằng cách sử dụng lệnh sau:
Dưới đây là mô tả trực quan hóa của AST:
Sau đó, Engine sẽ bắt đầu quá trình tạo ra Byte-code chính là code được thực thi.
Trình thông dịch / trình biên dịch cơ sở
Đây là phần khó hiểu nhất, JavaScript cũng là một ngôn ngữ biên dịch nhưng nó lại khác ngôn ngữ biên dịch cổ điển, ví dụ như C. Ngôn ngữ C là ngôn ngữ được nhập tĩnh bằng cách sử dụng biên dịch AOT, nhưng JavaScript là ngôn ngữ được nhập động và sử dụng biên dịch JIT. Phía dưới là một vài so sánh giữa 2 trình biên dịch này:
AOT (Ahead Of Time)
Biên dịch trước thời gian - quá trình biên dịch là riêng biệt và chỉ diễn ra một lần, kết quả của quá trình này là một tệp thực thi.
JIT (Just In Time)
Biên dịch không phải là một quá trình riêng biệt, nó diễn ra cùng với thời gian chạy. Engine sẽ tiến hành biên dịch từng dòng một và thu thập các thông tin về code, để có thể sử dụng sau này trong bước biên dịch lại.
V8 có một thành phần chịu trách nhiệm cho phần biên dịch này được gọi là Ignition, nếu bạn đã kiểm tra mã nguồn của V8, bạn sẽ thấy BytecodeGenerator là một phần của Ignition. Cũng trong quá trình biên dịch, JavaScript Engine thực hiện cấu hình (profiling) để thu thập một số thông tin về code để sử dụng nó sau này trong bước tối ưu hóa.
Bạn có thể xem Byte-code của bất kỳ mẫu code nào bằng lệnh:
Tại bước này, code JavaScript đang được chạy nên các bước tiếp theo sẽ nhằm mục đích tối ưu hóa.
Profiling
Profiling là một phần của bước thông dịch, Engine thu thập thông tin về đoạn code như kiểu dữ liệu và đánh dấu các chức năng thường được sử dụng (hot function) sau đó lưu trữ thông tin này trong Feedback Vector.
Bạn có thể xem những dữ liệu kể trên bằng lệnh:
Trình tối ưu hóa
TurboFan là trình tối ưu hóa bên trong V8. Dựa trên các thông tin mà Ignition thu thập được, TurboFan bắt đầu tối ưu hóa các hot function để đem lại hiệu suất tốt hơn. TurboFan phụ thuộc vào bộ nhớ đệm nội tuyến (inline caching) để lưu chức năng được tối ưu hóa này và sử dụng nó thay vì chức năng ban đầu.
Khi bộ nhớ đệm nội tuyến không hợp lệ, Engine sẽ lại thực thi Byte-code thay vì code đã được tối ưu hóa, việc này được gọi là deoptimization.
Cách tránh xảy ra Deoptimization
Có nhiều thủ thuật giúp tránh xảy ra deoptimization để trang web của bạn có hiệu suất tốt như:
Làm cho code của bạn trông giống như các ngôn ngữ nhập tĩnh.
Tránh các mảng holey.