Ngữ cảnh thực thi trong JavaScript
Ngữ cảnh thực thi là môi trường mà mã JavaScript được thực thi. Mọi hoạt động trong JavaScript xảy ra bên trong một ngữ cảnh thực thi, quản lý các biến, hàm và phạm vi của mã.
Các giai đoạn của ngữ cảnh thực thi
Mỗi ngữ cảnh thực thi được tạo ra và xử lý qua hai giai đoạn rõ ràng:
Giai đoạn tạo
- Tạo đối tượng biến: Trình biên dịch JavaScript quét mã và lưu trữ các khai báo hàm và biến trong bộ nhớ. Đối với các biến
var, nó gán giá trị mặc định làundefined. Hành vi này được gọi là hoisting. - Thiết lập chuỗi phạm vi: Trình biên dịch thiết lập chuỗi phạm vi, xác định nơi các biến có thể được truy cập. Một hàm có thể truy cập các biến từ ngữ cảnh của chính nó và các ngữ cảnh của các hàm cha, cho đến phạm vi toàn cục.
- Gán giá trị cho từ khóa
this: Từ khóathisđược gán một giá trị tùy thuộc vào cách mà mã được gọi.
Giai đoạn thực thi
- Trình biên dịch JavaScript chạy mã từng dòng một.
- Nó gán các giá trị thực tế cho các biến ban đầu được đặt là
undefinedtrong giai đoạn tạo. - Nó gọi các hàm và thực hiện các câu lệnh trong script.
Các loại ngữ cảnh thực thi
Có hai loại ngữ cảnh thực thi chính:
Ngữ cảnh thực thi toàn cục (GEC)
- Ngữ cảnh mặc định nơi mã được thực thi. Mỗi tệp JavaScript chỉ có một GEC.
- Nó tạo ra một đối tượng toàn cục (window trong trình duyệt hoặc global trong Node.js) và gán từ khóa
thischo nó. - Tất cả các biến và hàm toàn cục được lưu trữ dưới dạng thuộc tính và phương thức trên đối tượng toàn cục này.
Ngữ cảnh thực thi hàm (FEC)
- Được tạo ra mỗi khi một hàm được gọi.
- Khác với GEC, có thể có nhiều FEC trong một chương trình.
- Nó tạo ra phạm vi riêng cho các biến và một đối tượng
argumentsđặc biệt chứa tất cả các tham số được truyền vào hàm. - Khi việc thực thi của một hàm hoàn tất, ngữ cảnh của nó sẽ bị hủy.
Ngăn xếp gọi (Call Stack)
Trình biên dịch JavaScript sử dụng ngăn xếp gọi để quản lý tất cả các ngữ cảnh thực thi.
- Nó hoạt động theo nguyên tắc Last-In, First-Out (LIFO).
- Khi một script được chạy lần đầu, GEC sẽ được đưa vào ngăn xếp.
- Mỗi khi một hàm được gọi, FEC của nó sẽ được tạo và đưa lên đầu ngăn xếp, trở thành ngữ cảnh đang hoạt động.
- Khi một hàm hoàn tất việc thực thi, ngữ cảnh của nó sẽ được lấy ra khỏi ngăn xếp, và điều khiển trở lại ngữ cảnh bên dưới.
- Chương trình kết thúc khi GEC cuối cùng được lấy ra khỏi ngăn xếp.
Ví dụ thực tế
Dưới đây là một ví dụ minh họa:
javascript
function funcA(m, n) {
return m * n;
}
function funcB(m, n) {
return funcA(m, n);
}
function getResult(num1, num2) {
return funcB(num1, num2);
}
var res = getResult(5, 6);
console.log(res); // 30
Trong ví dụ này, trình biên dịch JavaScript tạo ra một ngữ cảnh thực thi toàn cục và đi vào giai đoạn tạo.
Đầu tiên, nó phân bổ bộ nhớ cho funcA, funcB, hàm getResult, và biến res. Sau đó, nó gọi getResult(), hàm này sẽ được đưa vào ngăn xếp gọi.
Tiếp theo, getResult() sẽ gọi funcB(). Tại thời điểm này, ngữ cảnh của funcB sẽ được lưu trữ ở đỉnh ngăn xếp. Sau đó, nó sẽ bắt đầu thực thi và gọi một hàm khác là funcA(). Tương tự, ngữ cảnh của funcA cũng sẽ được đưa vào ngăn xếp.
Khi việc thực thi của mỗi hàm hoàn tất, nó sẽ được gỡ bỏ khỏi ngăn xếp. Cả quá trình thực thi được diễn tả trong hình sau:
Thực hành tốt và những cạm bẫy phổ biến
Thực hành tốt
- Hiểu rõ về hoisting: Nắm vững cách hoạt động của hoisting giúp bạn tránh được nhiều lỗi không mong muốn.
- Sử dụng từ khóa
letvàconst: Thay vì sử dụngvar, hãy sử dụngletvàconstđể có phạm vi khối rõ ràng hơn.
Những cạm bẫy phổ biến
- Nhầm lẫn giữa
thistrong các hàm khác nhau: Từ khóathiscó thể khó hiểu, cần chú ý cách gọi hàm. - Không xử lý các ngoại lệ: Luôn luôn xử lý các ngoại lệ để tránh chương trình bị dừng đột ngột.
Mẹo hiệu suất
- Tránh lạm dụng closures: Sử dụng closures một cách hợp lý để không gây ra rò rỉ bộ nhớ.
- Tối ưu hóa vòng lặp: Sử dụng vòng lặp hiệu quả để cải thiện hiệu suất chạy của mã.
Giải quyết sự cố
- Kiểm tra ngăn xếp gọi: Sử dụng công cụ phát triển để kiểm tra ngăn xếp gọi khi gặp lỗi.
- Ghi lại các tham số và kết quả: Ghi lại các giá trị tham số và kết quả trong quá trình thực thi để xác định vấn đề.
Câu hỏi thường gặp
Ngữ cảnh thực thi là gì?
Ngữ cảnh thực thi là môi trường nơi mã JavaScript được thực thi, quản lý các biến, hàm và phạm vi.
Có bao nhiêu loại ngữ cảnh thực thi trong JavaScript?
Có hai loại: Ngữ cảnh thực thi toàn cục (GEC) và Ngữ cảnh thực thi hàm (FEC).
Làm thế nào để cải thiện hiệu suất khi làm việc với ngữ cảnh thực thi?
Sử dụng thực hành tốt, tránh lạm dụng closures và tối ưu hóa vòng lặp.
Kết luận
Ngữ cảnh thực thi là một khái niệm cốt lõi trong JavaScript mà mọi lập trình viên nên nắm rõ. Hiểu cách hoạt động của nó sẽ giúp bạn viết mã hiệu quả và dễ bảo trì hơn. Nếu bạn muốn tìm hiểu sâu hơn về JavaScript, đừng ngần ngại tham gia cộng đồng lập trình viên và chia sẻ kinh nghiệm của mình!