JavaScript Execution Contexts and Call Stacks
Bài đăng này đã không được cập nhật trong 3 năm
JavaScript
đã trở thành một trong những ngôn ngữ lập trình phổ biến nhất hiện nay. Nó cũng dẫn đầu về số lượng repository GitHub và là ngôn ngữ lập trình được thảo luận nhiều nhất trên StackOverflow
.
Do đó, điều rất quan trọng là bạn phải hiểu rõ những điều cơ bản và biết điều gì xảy ra đằng sau bất kỳ chương trình JS nào và nó được thực thi như thế nào, nếu bạn muốn đi sâu vào JS.
Execution Context (EC)
Mọi thứ trong JS đều xảy ra bên trong Execution Context
. Nó là môi trường mà mã JS được thực thi. Nó bao gồm giá trị của ‘this’, các biến, đối tượng và funtions mà mã JS có quyền truy cập vào bất kỳ thời điểm cụ thể nào dưới dạng các cặp key-value
. Mỗi code block
sẽ có EC
riêng mà nó đang thực thi.
Hãy nhìn vào hình phía trên, nó bao gồm hai phần
- Bộ nhớ (memory): Tất cả các biến có trong code của bạn được lưu trữ ở đây dưới dạng các cặp
key-value
- Code: Đây là một thread nơi code được thực thi, một dòng tại một thời điểm
Types of Execution Contexts
-
Global Execution Context (GEC) : Nó được tạo một lần cho mọi chương trình theo mặc định. Nó bao gồm code không có bên trong bất kỳ chức năng (function) nào. GEC chịu trách nhiệm chính về hai việc:
- nó tạo ra một đối tượng toàn cục (global object) là window object (dành cho các trình duyệt)
- nó đặt giá trị của
this
bằngglobal object
.
GEC bị clear sau khi quá trình thực thi toàn bộ chương trình kết thúc.
-
Function Execution Context (FEC) : Mỗi khi một hàm được gọi, một execution context sẽ được tạo cho hàm đó. Nó sẽ được clear khi hàm trả về thứ gì đó hoặc quá trình thực thi của nó kết thúc.
How is an Execution Context created
JavaScript Engine tạo Execution Context
trong 2 giai đoạn:
-
Creation Phase: ITrong giai đoạn này,
JS engine
chỉ quét toàn bộ code nhưng không thực thi nó. Nó tạoscope chain
rồi cấp phát bộ nhớ cho mọi biến (với giá trị của nó làundefined
) và cácfunction
trong phạm vi của nó. Sau đó, nó cũng khởi tạo giá trị củathis
. -
Execution Phase: Trong giai đoạn này, JS engine thực hiện quét lại code để cập nhật các biến và hoàn tất quá trình thực thi.
Bất cứ khi nào bạn chạy code JS của mình, trong Creation Phase
, một Global Execution Context
được tạo để lưu trữ tất cả các biến toàn cục có giá trị là undefined
và các function với phần thân của nó là giá trị. Sau đó, một unique EC được tạo cho các function khác hoạt động theo cùng một cách :
- trước tiên nó lưu trữ và cấp phát bộ nhớ cho tất cả các biến cục bộ của hàm đó
- thực thi block code và tự hủy sau khi
Execution Phase
của nó kết thúc.
Ví dụ:
var a = 10;
function doubleTheNumber(number) {
var doubleNumber = 2 * number;
return doubleNumber;
}
var result = doubleTheNumber(a);
console.log(result);
Với đoạn code trên, khi thực thi nó sẽ chạy như sau:
- Khi đoạn code trên được chạy, đầu tiên nó sẽ vào
Creation Phase
. Toàn bộ code đượcJS engine
scan vàGlobal Execution Context
được tạo.
- Trong lần scan thứ 2, khi nó đang trong
Execution Phase
, mỗi dòng code sẽ được scan từ trên xuống dưới và giá trị củaa
được cập nhật thành10
- bởi vì JavaScript là ngôn ngữ đồng bộ, đơn luồng nên nó lại phải scan lại từng dòng từ trên xuống. - Khi nó scan tới dòng
var result = doubleTheNumber(a)
. Nó sẽ vàofunction
này để quét.
- Bây giờ, để thực thi function này, các bước thực thi sẽ tương tự các bước trên. Một
EC
sẽ được tạo cho nó. Trongcreation phase
, bộ nhớ sẽ được tạo choDoubleNumber
.
- Trong giai đoạn thực thi của hàm này, vì giá trị của
number
là 10 nêndoubledNumber
sẽ là 2 * 10, tức là 20. Sau đó nó sẽ trả về20
- Sau câu lệnh
return
,execution context
cho hàmdoubleTheNumber
sẽ được hủy/clear và JS Engine quay trở lại dòngvar result = doubleTheNumber(a)
, nơi giá trị củaresult
sẽ được cập nhật thành 20. - Dòng cuối cùng của code được thực thi -
console.log(result);
- sau đóGlobal Execution Context
của chương trình này sẽ được hủy/clear.
Ở ví dụ trên, các bạn có thể hiểu được làm thế nào để một JS Program
được thực thi. Tuy nhiên, trong thực tế, quá trình thực thi của nó sẽ không thẳng tuột như các bước từ 1-7 như ở trên. JS Engine sử dụng CALL STACK
để quản lý và thực thi các bước này.
CALL STACK
Call Stack duy trì thứ tự thực hiện các Execution Contexts
. Nó còn được gọi bằng những cái tên như Program Stack
, Control Stack
, Runtime Stack
, v.v.
Nó là một ngăn xếp/stack bao gồm tất cả các EC. GEC luôn là EC đầu tiên được push vào ngăn xếp này và cũng là EC cuối cùng được pop ra. Bất cứ khi nào EC mới được tạo, nó sẽ được push vào stack. Khi quá trình thực thi của nó kết thúc hoặc nó trả về một giá trị nào đó, nó sẽ được pop ra ngoài và JS engine chuyển đến bước tiếp theo trong Call Stack.
Ví dụ:
var a = 10;
function doubleTheNumber(number) {
var doubleNumber = 2 * number;
return doubleNumber;
}
var result = doubleTheNumber(10);
console.log(result);
Giải thích hoạt động của call stack:
- Khi chạy đoạn code trên, GEC sẽ được tạo trước tiên và được đẩy vào ngăn xếp. Trong quá trình thực thi, khi JS Engine thực thi function
doubleTheNumber
, một EC mới sẽ được tạo riêng cho hàm này và được đẩy vào ngăn xếp. Khi quá trình thực thi kết thúc, EC này sẽ được lấy ra và JS Engite trở lại GEC. Sau khi thực thi hoàn toàn đoạn code này, GEC này cũng sẽ được lấy ra!
Tương tự, bạn cũng có thể kiểm tra thực tế cách hoạt động của Call Stack
cho bất kỳ mã JS nhất định nào.
Chạy code JS của bạn trong trình duyệt -> Open console -> Sources. Bạn sẽ Call Stack
như trong hình dưới đây:
Tại thời điểm này, Call Stack
sẽ trống vì code đã hoàn thành việc thực thi. Để xem việc tạo và xóa các EC, hãy thêm các breakpoints vào code của bạn và chạy thôi.
Conclusion
Phía trên, chúng ta đã cùng đi qua xem cách mà một đoạn code được thực thi trong JS như thế nào, cách mà JS quản lý các bước thực hiện ra sao. Mong rằng nó có thể giúp các bạn hiểu thêm về JS và nắm nó rõ hơn
Reference
All rights reserved