JavaScript Under The Hood: Vòng lặp sự kiện
Hãy làm chủ JavaScript bằng cách khám phá chức năng của nó từ đầu
Bạn đã bao giờ gặp lỗi không xác định hoặc gặp khó khăn trong việc xác định phạm vi của một biến chưa?
Thực sự tốn thời gian để gỡ lỗi bất kỳ lỗi nào mà không biết mã hoạt động như thế nào.
Trong blog này, tôi sẽ chứng minh các khái niệm nâng cao như event loopliên quan đến execution context, call stackvà callback queuethực sự hoạt động như thế nào.
Tuyên bố từ chối trách nhiệm — Các khái niệm này cực kỳ giàu kiến thức và có mối liên hệ với nhau, vì vậy đừng chớp mắt!
Công cụ JavaScript
Công cụ V8 của Google là một minh họa nổi tiếng về Công cụ JavaScript. Chẳng hạn, Chrome và Node.js đều sử dụng công cụ V8. Về cơ bản, động cơ V8 bao gồm hai phần —
- Ngăn xếp cuộc gọi: Tất cả mã được thực thi bên trong nó. Nó hoạt động giống như cấu trúc dữ liệu Stack, nghĩa là tuân theo khái niệm LIFO (Last In First Out).
- Memory Heap: Nơi diễn ra quá trình cấp phát bộ nhớ. Không giống như cấu trúc dữ liệu heap, nó chỉ là một bộ nhớ.
Bối cảnh thực thi toàn cầu hoặc GEC là Bối cảnh thực thi mặc định được tạo bởi công cụ JavaScript bất cứ khi nào nhận được tệp tập lệnh.
Tất cả mã JavaScript không nằm trong hàm được thực thi trong GEC. Các bước sau đây được thực hiện trong GEC—
- Xây dựng một không gian bộ nhớ để lưu trữ tất cả các biến và chức năng trên quy mô toàn cầu
- Tạo một đối tượng toàn cầu.
- Tạo từ khóa
this
Tùy thuộc vào nơi mã của bạn sẽ được thực thi sẽ xác định thisvị trí của nó. Chẳng hạn, trong Node.js, nó trỏ đến một đối tượng toàn cục riêng biệt, trong khi ở trình duyệt, nó trỏ đến windowđối tượng.
Ngăn xếp cuộc gọi (Hoặc Ngăn xếp thực thi chức năng)
JavaScript là single-threaded, như bạn đã nghe nói. Nhưng nó thực sự có nghĩa là gì?
Nó có nghĩa là một công cụ JavaScript chỉ chứa một call stackhoặc function execution stack.
- Như chúng ta biết, bất cứ khi nào trình biên dịch khám phá mã của bạn lần đầu tiên, công cụ JS sẽ được yêu cầu tạo
Global Execution ContexthoặcGECbởi trình biên dịch cũng như được yêu cầu đặt nó vào tệpCall Stack. - Toàn bộ mã của bạn được thực thi từng cái một trong
Global Execution Contextvà nó cấp phát bộ nhớ cho định nghĩa hàm hoặc khai báo biến và lưu trữ ở đó. - Nhưng khi bất kỳ lệnh gọi hàm nào được tìm thấy,
Functional Execution ContexthoặcFECđược tạo để thực thi mã của hàm và sau đó nó được thêm vào đầu tệpcall stack. - Trình thông dịch loại bỏ một chức năng khỏi
call stackmỗi khi chức năng kết thúc. Một chức năng kết thúc - khi nó đạt đến cuối phạm vi của nó hoặc một câu lệnh trả về. - Cuối cùng, việc thực thi toàn bộ mã của bạn
GECsẽ bị xóa khỏi tệpCall Stack.
Đừng lo lắng, hãy chứng minh điều đó bằng một ví dụ.
function f1{
console.log('f1');
}
f1();
console.log('end');
Bước 2: — Trong ví dụ của chúng tôi, dòng đầu tiên sẽ được thực thi là f1. Một bộ nhớ sẽ được cấp phát f1và lưu trữ định nghĩa của nó.
Bước 3: — Ở dòng thứ 2, một chức năng được gọi. Đối với lời gọi hàm này, một Function Execution Context hoặc FECsẽ được tạo và nó sẽ được lưu trữ trên đầu tệp Call Stack.
Bước 4: — Bây giờ, toàn bộ f1()sẽ được thực thi từng dòng một và sau khi thực hiện xong, nó sẽ bị xóa khỏi tệp Call Stack.
Bước 5: — Sau đó, dòng cuối cùng console.log('end')sẽ được thực thi và sẽ in 'end' trên bảng điều khiển. Cuối cùng, việc thực thi toàn bộ mã của bạn, Global Execution Contextsẽ bị xóa khỏi tệp Call Stack.
JS quản lý các tác vụ không đồng bộ như thế nào?
JavaScript, như tất cả chúng ta đều biết, là một ngôn ngữ đơn luồng , đồng bộ (từng nhiệm vụ một lần) và một luồng đơn thực thi ngay lập tức bất cứ thứ gì có bên trong nó.call stack
Nhưng nếu chúng ta cần thực hiện một cái gì đó sau 5 giây thì sao? Chúng ta có thể làm điều đó bên trong call stack?
Không, chúng ta không thể. Bởi vì call stackkhông có bất kỳ bộ đếm thời gian. Nhưng làm thế nào chúng ta có thể làm điều đó?
Đây là nơi thời gian chạy JavaScript xuất hiện.
Có thể rất đau lòng nếu tôi nói với bạn bây giờ- setTimeout()không phải là một phần của JavaScript, mặc dù console.log()hoặc tất cả các sự kiện DOM đều là một phần của API Web cấp quyền truy cập vào Công cụ JavaScript để sử dụng tất cả các thuộc tính của nó trong Bối cảnh thực thi toàn cầu (GEC) thông qua đối tượng toàn cục window. Các API web này được gọi là không đồng bộ .
Hàng đợi gọi lại là gì?
Một hàng đợi các tác vụ được gọi là "hàng đợi gọi lại" hoặc "hàng đợi tác vụ" là một hàng đợi được thực thi sau khi hoàn thành các nhiệm vụ hiện tại của ngăn xếp cuộc gọi. Các tác vụ được đăng ký trong API web sẽ chuyển từ API web vào hàng đợi gọi lại.
Hàng đợi gọi lại hoạt động giống như cấu trúc dữ liệu hàng đợi, nghĩa là các tác vụ được xử lý theo thứ tự FIFO (vào trước, ra trước), trái ngược với ngăn xếp cuộc gọi, nghĩa là các tác vụ được xử lý theo thứ tự chúng được thêm vào hàng đợi.
Vòng lặp sự kiện là gì?
Vòng lặp sự kiện JavaScript thêm tác vụ từ hàng đợi gọi lại vào ngăn xếp cuộc gọi theo thứ tự FIFO ngay khi ngăn xếp cuộc gọi trống.
Vòng lặp sự kiện bị chặn nếu ngăn xếp cuộc gọi hiện đang chạy một số mã và sẽ không thêm các cuộc gọi bổ sung từ hàng đợi cho đến khi ngăn xếp trống một lần nữa. Điều này là do mã JavaScript được chạy theo kiểu chạy đến khi hoàn thành.
Hãy hiểu các khái niệm trên với một ví dụ.
- Lúc đầu,
Global Execution Contextđược tạo cho mã của chúng tôi bên trong của chúng tôicall stackvàGECthực thi từng dòng mã của chúng tôi. GECthực thi dòng đầu tiên và in 'Bắt đầu' trên bảng điều khiển của chúng tôi.- Thực thi dòng thứ 2,
setTimeout()API web sẽ được gọi và sau đósetTimeout()cấp quyền truy cập tính năng hẹn giờ. Sau đó, bạn sẽ có thể đặt5000mslàm thời gian trễ. - Khi bạn chuyển
callBack()chức năng quasetTimeout(), chức năngcallBack()sẽ được đăng ký dưới dạng Gọi lại qua API Web web. - Và sau đó
GECthực hiện dòng đầu tiên cũng như in 'Kết thúc' trên bảng điều khiển. - Khi tất cả mã được thực thi,
GECsẽ bị xóa khỏi tệpcall stack. - Sau đó
5000 millisecond,callBack()chức năng được đăng ký trong tệpweb API, được chuyển vào bên trong tệpcall Back Queue. Event loopđặtcallBack()chức năng vàocall Stackkhi hoàn thành thì tất cả đều hoạt động. Và cuối cùng,callBack()chức năng được thực thi và in 'Gọi lại' vào bảng điều khiển.
function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
}
function main() {
console.log('main');
setTimeout(f1, 0);
f2();
}
main();
Nếu bạn đang nghĩ “f1” sẽ được in trước “f2” thì bạn đã nhầm. Nó sẽ là -
main
f2
f1
Cơ chế được thảo luận của JavaScript phù hợp với bất kỳ chức năng gọi lại hoặc yêu cầu API nào.
Hãy quan sát từng bước ví dụ thứ 2 hoạt động như thế nào trong môi trường thời gian chạy.
- Lúc đầu , mã
GECsẽ được tạo bên trongcall stackvà sau đó mã sẽ được thực thi từng dòng trong tệpGEC. Nó lưu trữ tất cả các định nghĩa chức năng trong Memory Heap . - Khi
main()được gọi, aFunction Execution Context (FEC)được tạo và sau đó nó được đưa vào bên trong ngăn xếp cuộc gọi. Sau đó, tất cả mã củamain()hàm sẽ được thực thi từng dòng một. - Nó có một bản ghi giao diện điều khiển để in từ chính. Vì vậy,
console.log('main')thực thi và đi ra khỏi ngăn xếp. setTimeout()API trình duyệt diễn ra . Chức năng gọi lại đặt nó vào hàng đợi gọi lại. Nhưng trong ngăn xếp, việc thực thi diễn ra như bình thường, vì vậy hãyf2()vào ngăn xếp. Nhật ký giao diện điều khiển của cácf2()lần thực thi. Cả hai đi ra khỏi ngăn xếp.- Và sau đó
main()cũng bật ra khỏi ngăn xếp. - Vòng lặp sự kiện nhận ra rằng ngăn xếp cuộc gọi trống và có chức năng gọi lại trong hàng đợi. Vì vậy, chức năng gọi lại
f1()sẽ được đưa vào ngăn xếp bởievent loop. Thực hiện bắt đầu. Nhật ký bảng điều khiển thực thi vàf1()cũng bật ra khỏi ngăn xếp. Và cuối cùng, không có gì khác trong ngăn xếp và hàng đợi để thực thi tiếp.
Đó là thực hành và lưu ý của tôi. Nếu bạn thấy nó hữu ích, vui lòng thể hiện sự ủng hộ của bạn bằng cách nhấp vào biểu tượng vỗ tay bên dưới. Bạn có thể theo dõi tôi trên phương tiện .

![Dù sao thì một danh sách được liên kết là gì? [Phần 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































