JavaScript Engine giống như phiên dịch viên của chúng ta vậy, nó giúp chúng ta giao tiếp với máy tính.
Bạn và máy tính nói và hiểu 2 ngôn ngữ hoàn toàn khác nhau, bạn có thể hiểu tiếng Anh, Việt, Nga, vân vân. Nhưng máy tính chỉ hiểu 0 và 1. Vậy nên cần có một thứ đứng ở giữa giúp cho máy tính hiểu bạn yêu cầu nó làm gì.
Giả sử bạn muốn máy tính hiển thị cho bạn danh sách ảnh mấy em gái xinh chẳng hạn, và bạn chẳng có gì ngoài 1 file gaixinh.js. Bạn không thể nói với cái máy tính là "Ê, cho t xem ảnh mấy em gái xinh đê".
Lúc này JS Engine ra đời, nó giúp biên dịch file .js của bạn thành ngôn ngữ mà máy tính có thể hiểu.
Có rất nhiều loại JS Engine được viết bởi nhiều lập trình viên khác nhau. Bạn có thể xem danh sách các JS Engine này tại đây. Chúng được gọi là ECMAScript engine (lý do tại sao có cái tên này mình sẽ nói ở một bài khác). Engine mà chúng ta dùng phổ biến nhất hiện nay là V8 Engine của Google, được viết bằng C++, dùng trong Chrome của chúng ta.
Vậy tại sao các lập trình viên lại viết ra các engine này?
Để nói về vấn đề này thì chúng ta cùng nhau quay lại những năm V8 Engine mới ra đời.
Trước khi Google cho ra mắt V8 Engine thì các JS Engine trước đó có thể nói khá là...cùi. Google lúc đó đang cho chạy Google Maps, mà các bạn biết thằng Google Maps xử lý rất nhiều logic phức tạp liên quan đến đồ hoạ như phóng to, thu nhỏ, chỉ đường,... Dẫn đến nó chạy khá là lag trên các trình duyệt kể cả Chrome (vì sử dụng JS Engine cùi bắp). Và thế là V8 Engine ra đời, mục đích đầu tiên là phục vụ cho thằng Google Maps chạy mượt, tiếp đến là các sản phẩm trong hệ sinh thái của Google thời đó cũng mượt theo đặc biệt là Google Search (sản phẩm chính của Google thời đó) → doanh thu của Google tăng.
Vậy thì JavaScript Engine hoạt động như thế nào?
Để hiểu được cách hoạt động của JS Engine, bạn sẽ cần nắm được các khái niệm sau:
Interpreter (thông dịch)
Hãy cùng nhau xem đoạn code sau:
function calculator(x, y) { return x + y;
}
for (let i = 0; i < 1000; i++) { calculator(30, 20)
}
Trình tự đoạn code này chạy sẽ là: Khởi tạo function calculator() → Chạy vào hàm for loop và chạy function calculator 1000 lần Các bạn sẽ thấy mỗi lần hàm calculator được chạy, nó sẽ tính lại x + y và trả về kết quả là 50. Và cái trình duyệt của chúng ta sẽ phải gánh gãy lưng vì cái hàm này chạy 1000 lần. Đương nhiên trong thực tế không có ai sử dụng vòng for như kia cả, nhưng vẫn sẽ có rất nhiều trường hợp tương tự. Interpreter là cơ chế chạy tuần tự của JS, nó sẽ đọc từng dòng và thực hiện tuần tự từ trên xuống, không có bất kỳ sự tối ưu nào. Vì thế mới có thêm một thứ được gọi là Compiler.
Compiler (biên dịch)
Compiler sẽ quét toàn bộ file js, do phải quét toàn bộ file js nên việc khởi chạy của thằng Compiler khá chậm, không nhanh như Interpreter. Compiler sẽ giúp tìm ra chỗ nào chưa được tối ưu và xử lý lại. Quay lại đoạn code bên trên, Compiler thấy hàm calculator chạy đến lần thứ 3 vẫn trả ra kết quả là 50, nên từ lần thứ 4 thay vì phải tính lại x + y thì nó trả luôn ra text là 50, vậy là từ những lần sau hàm calculator sẽ thực thi như này:
function calculator(x, y) { return 50;
}
Vậy là khỏi cần tính lại cho mệt.
Giờ bạn thử đoán xem, JavaScript sử dụng thông dịch hay biên dịch?
Câu trả lời là cả 2. Thực ra bản chất JavaScript chỉ là ngôn ngữ thông dịch, nhưng nhờ có V8 Engine mà giờ đây JS như được buff thêm sức mạnh, 2 đứa kết hợp lại gọi là JIT Compiler (Just in time Compiler). Giả sử JS chỉ có Interpreter mà không có Compiler thì ra sao, nó sẽ rất chậm, giống như mình đã giải thích ở trên, việc chạy tuần tự mà không có sự tối ưu sẽ khiến trình duyệt của chúng ta rất vất vả khi xử lý nhiều tác vụ phức tạp.
Vậy tóm lại quy trình chạy của JS Engine là như nào?
Khi trình duyệt của chúng ta nhận được 1 đoạn code JS, nó sẽ kiểm tra xem đoạn code đấy có hợp lệ không (có đúng là JavaScript không hay bạn gõ code C# vào đấy), parser (trình phân tích cú pháp) sẽ chạy từng dòng code để kiểm tra tính hợp lệ. Nếu code của bạn hợp lệ, parser sẽ tạo ra một cấu trúc dữ liệu gọi là AST (Abstract Syntax Tree), thứ sẽ dịch code của bạn sang mã máy (machine code). Code sau khi được dịch sẽ không còn là code JS nữa, nhưng bộ xử lý của máy tính đã có thể đọc được code này. Bạn có thể vào đây để xem ví dụ trực quan hơn: https://astexplorer.net/
Sau khi AST xử lý, nó sẽ chuyển đến Interpreter. Interpreter sẽ dịch chúng sang Bytecode
Trước khi đến được Compiler, code sẽ phải đi qua thứ được gọi là Profiler.
Profiler giống như một người QC vậy, nó sẽ theo dõi thằng Interpreter, chỗ nào chưa được tối ưu nó sẽ bảo với thằng Compiler là: "Chỗ này chưa ổn này cu, xử lý lại giúp tao cái". Và lúc này thằng Compiler vào việc. Compiler sau khi xử lý xong những đoạn code chưa được tối ưu thì tiếp tục dịch Bytecode sang Machine code để CPU của chúng ta có thể đọc được.
Và thế là xong, trình duyệt sẽ trả về kết quả mà chúng ta mong muốn từ đoạn code ta đưa vào.