JavaScript có mô hình thời gian chạy dựa trên vòng lặp sự kiện, chịu trách nhiệm thực thi mã, thu thập và xử lý các sự kiện và thực hiện các nhiệm vụ phụ hàng đợi. Mô hình này khá khác biệt so với các mô hình trong các ngôn ngữ khác như C và Java.event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. This model is quite different from models in other languages like C and Java. Các phần sau đây giải thích một mô hình lý thuyết. Động cơ JavaScript hiện đại thực hiện và tối ưu hóa mạnh mẽ các ngữ nghĩa được mô tả.
Khái niệm thời gian chạy
Đại diện trực quan
Cây rơm
Các cuộc gọi chức năng tạo thành một chồng khung.
function foo[b] {
const a = 10;
return a + b + 11;
}
function bar[x] {
const y = 3;
return foo[x * y];
}
const baz = bar[7]; // assigns 42 to baz
Thứ tự hoạt động:
- Khi gọi
bar
, khung đầu tiên được tạo có chứa các tham chiếu đến các đối số và biến cục bộ của ____ 4. - Khi
bar
gọifoo
, khung thứ hai được tạo và đẩy lên trên cái đầu tiên, chứa các tham chiếu đến các đối số và biến cục bộ của ____ 7. - Khi
foo
trả về, phần tử khung trên cùng được bật ra khỏi ngăn xếp [chỉ để lại khung cuộc gọi của ____ 4]. - Khi
bar
trở lại, ngăn xếp trống.
Lưu ý rằng các đối số và biến cục bộ có thể tiếp tục tồn tại, vì chúng được lưu trữ bên ngoài ngăn xếp - vì vậy chúng có thể được truy cập bởi bất kỳ chức năng lồng nhau nào từ lâu sau khi hàm bên ngoài của chúng trở lại.
Đống
Các đối tượng được phân bổ trong một đống chỉ là một tên để biểu thị một vùng bộ nhớ lớn [chủ yếu là không cấu trúc].
Xếp hàng
Thời gian chạy JavaScript sử dụng hàng đợi tin nhắn, đây là danh sách các tin nhắn được xử lý. Mỗi tin nhắn có một chức năng liên quan được gọi để xử lý tin nhắn.
Tại một số thời điểm trong vòng lặp sự kiện, thời gian chạy bắt đầu xử lý các tin nhắn trên hàng đợi, bắt đầu với cái cũ nhất. Để làm như vậy, thông báo được xóa khỏi hàng đợi và chức năng tương ứng của nó được gọi với thông báo dưới dạng tham số đầu vào. Như mọi khi, gọi một hàm tạo ra một khung ngăn xếp mới cho việc sử dụng chức năng đó.
Việc xử lý các chức năng tiếp tục cho đến khi ngăn xếp một lần nữa trống. Sau đó, vòng lặp sự kiện sẽ xử lý thông báo tiếp theo trong hàng đợi [nếu có một].
Vòng lặp sự kiện
Vòng lặp sự kiện có tên của nó vì cách nó thường được thực hiện, thường giống với:event loop got its name because of how it's usually implemented, which usually resembles:
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
2 chờ đồng bộ cho một tin nhắn đến [nếu một người chưa có sẵn và chờ được xử lý]."Run-to-completion"
Mỗi tin nhắn được xử lý hoàn toàn trước khi bất kỳ tin nhắn nào khác được xử lý.
Điều này cung cấp một số thuộc tính tốt đẹp khi lý luận về chương trình của bạn, bao gồm cả thực tế là bất cứ khi nào một hàm chạy, nó không thể được ưu tiên và sẽ chạy hoàn toàn trước khi bất kỳ mã nào khác chạy [và có thể sửa đổi dữ liệu mà chức năng thao túng]. Điều này khác với C, ví dụ, nếu một hàm chạy trong một luồng, nó có thể bị dừng tại bất kỳ điểm nào bởi hệ thống thời gian chạy để chạy một số mã khác trong một luồng khác.
Một nhược điểm của mô hình này là nếu một thông báo mất quá nhiều thời gian để hoàn thành, ứng dụng web không thể xử lý các tương tác của người dùng như nhấp hoặc cuộn. Trình duyệt giảm thiểu điều này với hộp thoại "A Script mất quá nhiều thời gian để chạy". Một thực tế tốt để làm theo là thực hiện xử lý tin nhắn ngắn và nếu có thể cắt giảm một tin nhắn thành một số tin nhắn.
Thêm tin nhắn
Trong các trình duyệt web, tin nhắn được thêm vào bất cứ lúc nào một sự kiện xảy ra và có một trình nghe sự kiện được đính kèm với nó. Nếu không có người nghe, sự kiện bị mất. Vì vậy, một cú nhấp chuột vào một phần tử với một trình xử lý sự kiện nhấp vào sẽ thêm một thông báo - tương tự như vậy với bất kỳ sự kiện nào khác.
Hàm
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 được gọi với 2 đối số: một thông báo để thêm vào hàng đợi và giá trị thời gian [tùy chọn; mặc định là while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
4]. Giá trị thời gian biểu thị độ trễ [tối thiểu] sau đó tin nhắn sẽ được đẩy vào hàng đợi. Nếu không có thông báo nào khác trong hàng đợi và ngăn xếp trống, thông báo sẽ được xử lý ngay sau khi trì hoãn. Tuy nhiên, nếu có tin nhắn, tin nhắn while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 sẽ phải chờ các tin nhắn khác được xử lý. Vì lý do này, đối số thứ hai cho thấy thời gian tối thiểu - không phải là thời gian được đảm bảo.Dưới đây là một ví dụ chứng minh khái niệm này [
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 không chạy ngay sau khi bộ đếm thời gian của nó hết hạn]:const seconds = new Date[].getTime[] / 1000;
setTimeout[[] => {
// prints out "2", meaning that the callback is not called immediately after 500 milliseconds.
console.log[`Ran after ${new Date[].getTime[] / 1000 - seconds} seconds`];
}, 500]
while [true] {
if [new Date[].getTime[] / 1000 - seconds >= 2] {
console.log["Good, looped for 2 seconds"];
break;
}
}
Không chậm trễ
Không chậm trễ không có nghĩa là cuộc gọi lại sẽ bị sa thải sau khi 0 mili giây. Gọi
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 với độ trễ là while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
4 [không] mili giây không thực thi chức năng gọi lại sau khoảng thời gian đã cho.Việc thực hiện phụ thuộc vào số lượng các nhiệm vụ chờ đợi trong hàng đợi. Trong ví dụ dưới đây, thông báo
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
9 sẽ được ghi vào bảng điều khiển trước khi thông báo trong cuộc gọi lại được xử lý, vì độ trễ là thời gian tối thiểu cần thiết cho thời gian chạy để xử lý yêu cầu [không phải thời gian đảm bảo].while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 cần chờ tất cả các mã cho các tin nhắn được xếp hàng hoàn thành mặc dù bạn đã chỉ định một giới hạn thời gian cụ thể cho while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 của mình.[[] => {
console.log['this is the start'];
setTimeout[[] => {
console.log['Callback 1: this is a msg from call back'];
}]; // has a default time value of 0
console.log['this is just a message'];
setTimeout[[] => {
console.log['Callback 2: this is a msg from call back'];
}, 0];
console.log['this is the end'];
}][];
// "this is the start"
// "this is just a message"
// "this is the end"
// "Callback 1: this is a msg from call back"
// "Callback 2: this is a msg from call back"
Một số thời gian giao tiếp cùng nhau
Một nhân viên web hoặc một bộ phận có nguồn gốc chéo
const seconds = new Date[].getTime[] / 1000;
setTimeout[[] => {
// prints out "2", meaning that the callback is not called immediately after 500 milliseconds.
console.log[`Ran after ${new Date[].getTime[] / 1000 - seconds} seconds`];
}, 500]
while [true] {
if [new Date[].getTime[] / 1000 - seconds >= 2] {
console.log["Good, looped for 2 seconds"];
break;
}
}
2 có hàng xếp hàng, heap và tin nhắn riêng. Hai Runtimes riêng biệt chỉ có thể giao tiếp thông qua việc gửi tin nhắn thông qua phương thức const seconds = new Date[].getTime[] / 1000;
setTimeout[[] => {
// prints out "2", meaning that the callback is not called immediately after 500 milliseconds.
console.log[`Ran after ${new Date[].getTime[] / 1000 - seconds} seconds`];
}, 500]
while [true] {
if [new Date[].getTime[] / 1000 - seconds >= 2] {
console.log["Good, looped for 2 seconds"];
break;
}
}
3. Phương thức này thêm một thông báo cho thời gian chạy khác nếu phương pháp sau lắng nghe các sự kiện const seconds = new Date[].getTime[] / 1000;
setTimeout[[] => {
// prints out "2", meaning that the callback is not called immediately after 500 milliseconds.
console.log[`Ran after ${new Date[].getTime[] / 1000 - seconds} seconds`];
}, 500]
while [true] {
if [new Date[].getTime[] / 1000 - seconds >= 2] {
console.log["Good, looped for 2 seconds"];
break;
}
}
4.Không bao giờ chặn
Một thuộc tính rất thú vị của mô hình vòng lặp sự kiện là JavaScript, không giống như nhiều ngôn ngữ khác, không bao giờ khối. Xử lý I/O thường được thực hiện thông qua các sự kiện và cuộc gọi lại, vì vậy khi ứng dụng đang chờ truy vấn IndexedDB để trả về hoặc yêu cầu XHR để trả về, nó vẫn có thể xử lý những thứ khác như đầu vào của người dùng.
Các ngoại lệ di sản tồn tại như
const seconds = new Date[].getTime[] / 1000;
setTimeout[[] => {
// prints out "2", meaning that the callback is not called immediately after 500 milliseconds.
console.log[`Ran after ${new Date[].getTime[] / 1000 - seconds} seconds`];
}, 500]
while [true] {
if [new Date[].getTime[] / 1000 - seconds >= 2] {
console.log["Good, looped for 2 seconds"];
break;
}
}
5 hoặc XHR đồng bộ, nhưng nó được coi là thực hành tốt để tránh chúng. Coi chừng: Các ngoại lệ cho ngoại lệ tồn tại [nhưng thường là các lỗi thực hiện, thay vì bất cứ điều gì khác].