Trang chủ Javascript Nguyên lý Javascript thực thi chương trình

Nguyên lý Javascript thực thi chương trình

Nguyễn Dương 20-06-2024

Javascript là ngôn ngữ lập trình bậc cao, hướng đối tượng, đa mô hình, có cơ chế quản lý bộ nhớ, được thông dịch hoặc biên dịch JIT, có tính động, đơn luồng, có các hàm first-class và mô hình đồng thời non-blocking event loop.

Mọi chương trình chạy trên máy tính của bạn đều cần một số tài nguyên phần cứng, chẳng hạn như bộ nhớ và CPU. Các ngôn ngữ cấp thấp, chẳng hạn như C thì chúng ta phải yêu cầu máy tính cung cấp bộ nhớ để tạo một biến mới. Mặt khác, có các ngôn ngữ cấp cao như JavaScript và Python, nơi chúng ta không phải quản lý tài nguyên vì những ngôn ngữ này có các phần trừu tượng được gọi và loại bỏ tất cả công việc đó khỏi chúng ta. Điều này làm cho ngôn ngữ bậc cao dễ học và dễ sử dụng hơn, nhưng nhược điểm là các chương trình bậc cao sẽ không bao giờ nhanh hoặc được tối ưu hóa như chương trình C.

Cơ chế quản lý bộ nhớ (garbage collected) là một thuật toán bên trong công cụ Javascript tự động loại bỏ các đối tượng cũ, đối tượng lâu không sử dụng khỏi bộ nhớ máy tính. Nó giống như một công cụ dọn dẹp, dọn sạch bộ nhớ theo thời gian.

Cơ chế thông dịch hoặc biên dịch JIT (biên dịch trong khi chạy): Bộ xử lý của máy tính chỉ có thể hiểu 0 và 1. Vì vậy mọi chương trình cần được viết bằng 0, 1 hay còn gọi là mã máy (machine code). Với ngôn ngữ lập trình, cụ thể là Javascript, code được viết ra để con người có thể hiểu được nên nó là một sự trừu tượng hóa dựa trên mã máy. Tuy nhiên code Javascript vẫn phải được dịch sang mã máy để máy có thể hiểu được, quá trình này gọi là thông dịch hoặc biên dịch.

- Paradigm là một cách tiếp cận và tư duy tổng thể về cấu trúc code. Chúng ta sẽ định hướng phong cách và kỹ thuật viết code trong một dự án dựa trên một mô hình nhất định. 3 mô hình lập trình phổ biến hiện nay là:

+ Lập trình thủ tục - Procedural programming: Sắp xếp code một cách tuyến tính, từ trên xuống dưới

+ Lập trình hướng đối tượng - Object oriented programming OOP

+ Lập trình hướng hàm - Functional Programming

- Ngoài ra, có thể phân loại paradigm thành 2 loại: Imperative và Declarative.

 

- Hầu hết mọi thứ trong Javascript đều là đối tượng ngoại trừ các giá trị nguyên thủy.

- Mảng cũng là một đối tượng, việc chúng ta có thể gọi phương thức push() trên mảng là do kế thừa theo prototype. Prototype giống như một template, array prototype chứa tất cả các phương thức của mảng.

 

Tính dynamic ở đây thực ra là dynamic-typed (cho phép thay đổi kiểu biến tùy biến), trong Javascript chúng ta không chỉ định được kiểu dữ liệu cho các biến, thay vào đó chúng chỉ được chỉ định khi JS thực thi code. Ngoài ra kiểu dữ kiệu cũng dễ dàng thay đổi khi chúng ta gán lại dữ liệu.

 

- JS là ngôn ngữ lập trình đơn luồng, có nghĩa là chúng chỉ có thể làm từng việc một nên chúng cần có mô hình Concurrency model.

- Concurrency model (mô hình đồng thời) là cách mà công cụ Javascript xử lý nhiều tác vụ cùng một lúc. 

- Với đơn luồng, nếu một nhiệm vụ kéo dài nó sẽ chặn cả luồng đơn đang chạy, khi đó chúng ta cần trạng thái non-blocking (không dừng).

- Event loop thực hiện nhiều tác vụ lâu dài và thực thi nó ở background sau đó đưa chúng trở lại luồng chính sau khi chúng hoàn thành.

Tóm lại JS là một mô hình đồng thời có non-blocking event loop với một luồng duy nhất.

 

Javascript Engine

- Javascript Engine là một chương trình máy tính thực thi code Javascript.

- Mỗi trình duyệt hiện nay đều có Javascript Engine, công cụ được biết đến nhiều nhất là Google V8.

- Bất kỳ JS engine nào cũng chứa callstack và heap.

- CallStack là nơi code được thực thi bằng cách sử dụng Execution Context.

- Heap là một vùng nhớ (memory pool) không có cấu trúc, lưu trữ tất cả các đối tượng mà ứng dụng cần.

 

Phân biệt giữa thông dịch và biên dịch

- Trong quá trình biên dịch, toàn bộ code được chuyển đổi thành mã máy cùng một lúc, sau đó mã máy này được viết thành một file di động có thể thực thi trên bất kỳ máy tính nào.

- Quá trình thông dịch chạy code và thực hiện từng dòng một, code được đọc và thực thi tất cả cùng một lúc. Code javascript vẫn được chuyển sang mã máy nhưng sau đó được thực thi luôn

JS kết hợp sử dụng cả thông dịch và biên dịch

- JS đã từng là một ngôn ngữ thông dịch thuần túy nhưng vấn đề của trình thông dịch là chúng chậm hơn nhiều lần so với biên dịch, với web hiện đại ngày nay điều đó không được chấp nhận nữa. JS ngày nay kết hợp vừa thông dịch vừa biên dịch được gọi là just-in-time compilation.

- JIT compiler hiểu cơ bản là biên dịch toàn bộ code thành mã máy cùng một lúc sau đó thực thi nó ngay lập tức. Điều này khiến JS thực thi nhanh hơn so với thông dịch trước đây.

 

Trình biên dịch just-in-time

1. Phân tích cú pháp (Pharsing): Khi một đoạn code JS đi vào JS Engine code sẽ được phân tích thành một cấu trúc dữ liệu được gọi là cây cú pháp trừu tượng (Abstract Syntax Tree), bước này cũng kiểm tra xem code có lỗi cú pháp nào không. Cây kết quả sẽ được sử dụng để tạo Mã máy.

Lưu ý: Cây cú pháp trừu tượng không liên quan gì đến cây DOM.

2. Biên dịch (Compilation) lấy AST được tạo ra và biên dịch nó thành mã máy.

3. Thực thi mã máy (Execution), việc thực thi diễn ra trong Call Stack.

Sau bước 3 JS Engine đã tạo một phiên bản đầu tiên, sau đó JS Engine thực hiện thêm một số chiến lược tối ưu hóa thực hiện trong background và biên dịch lại trong quá trình thực thi chương trình đã chạy.

 

JS runtime

Javascript Runtime trong trình duyệt như một hộp, thùng chứa lớn chứa tất cả những thứ cần thiết để sử dụng Javascript. Bao gồm:

- JS Engine (callstack và heap)

- Web APIs

- Callback Queue

 

Web APIs

- JS Runtime bao gồm Web APIs (các chức năng được cung cấp cho engine).

- JS có quyền truy cập vào các API này thông qua đối tượng global window.

 

Callback Queue

- Callback Queue là cấu trúc dữ liệu có chứa tất cả các hàm callback sẵn sàng để thực thi.

Ví dụ: Hàm được truyền vào để xử lý sự kiện trên một phần từ DOM cũng là một hàm callback.

- Sau khi một event xảy ra, hàm callback được đặt vào Callback Queue, sau đó khi stack rỗng, hàm callback được đặt vào callstack để nó thực thi, quá trình này xảy ra theo cơ chế event loop.

 

Event Loop

- Event Loop nhận các hàm callback từ Callback Queue và đưa chúng vào Call Stack để thực thi

 

JS không chỉ chạy trên trình duyệt

- Tồn tại nhiều kiểu JS Runtime khác nhau và JS không chỉ chạy trên trình duyệt.

 

Định nghĩa this

- Biến this là một biến đặc biệt được tạo trong mọi ngữ cảnh thực thi (Execution Context) và cho mọi function nào.

- This trỏ tới giá trị của chủ sở hữu của hàm nơi mà this được gọi.

- This không phải static, nó phụ thuộc vào cách mà hàm được gọi - giá trị của this chỉ được gán khi hàm thực sự được gọi.

 

Quá trình thực thi chi tiết của 1 chương trình JS

Sau khi quá trình biên dịch hoàn tất, mã máy được đưa vào quá trình thực thi:

1. Tạo ra global execution context (ngữ cảnh thực thi toàn cục) - dành cho những code thuộc top-level (code thuộc top-level là những code không nằm trong bất kỳ function nào)

- Execute context là ngữ cảnh thực thi định nghĩa là môi trường thực thi đoạn code JS, nó giống như một chiếc hộp lưu giữ tất cả các thông tin cần thiết để một code được thực thi như là các biến cục bộ hay các đối số truyền vào.

- Code JS luôn chạy trong một Execution Context.

- Trong bất kỳ project JS nào cũng chỉ có một global execution context là context mặc định để thực thi các code thuộc top level.

2. Thực thi top level code bên trong GEC (Global Execution Context)

3. Thực thi các hàm và chờ callback

- Với mỗi hàm sẽ có một execution context.

- Các EC này kết hợp vs nhau tạo thành Call Stack.

 

Bên trong Execution Context bao gồm:

1. Variable Environment chứa các biến, khai báo hàm và cũng có một object argument đặc biệt (chứa tất cả các đối số được truyền vào hàm thuộc về EC hiện tại)

2. Scope Chain bao gồm các tham chiếu đến các biến nằm ngoài hàm hiện tại

3. Từ khóa this

Lưu ý: EC thuộc về hàm mũi tên không lấy được arguments và cũng không có this.

 

- Call Stack là nơi các Execution Context xếp chồng lên nhau, EC ở trên cùng của stack là thứ hiện đang chạy và khi chạy xong nó sẽ bị xóa khỏi stack.

- Việc thực thi diễn ra theo nguyên lý của Stack - Last In First Out.

 

Bài viết liên quan

Từ khóa this Từ khóa this
Scope và scope chain trong javascript Scope và scope chain trong javascript
Nguyên lý Javascript thực thi chương trình Nguyên lý Javascript thực thi chương trình
Bất đồng bộ trong javascript Bất đồng bộ trong javascript
Lập trình hướng đối tượng Lập trình hướng đối tượng
Event trong javascript Event trong javascript