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ý sự kiện cũng như thực hiện các tác vụ phụ được xếp hàng đợi. Mô hình này khá khác so với các mô hình trong các ngôn ngữ khác như C và Java
khái niệm thời gian chạy
Các phần sau đây giải thích một mô hình lý thuyết. Các công cụ JavaScript hiện đại triển khai và tối ưu hóa mạnh mẽ ngữ nghĩa được mô tả
Đại diện trực quan
Cây rơm
Các lời gọi hàm tạo thành một ngăn xếp các 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ự các thao tác
- Khi gọi
8, khung đầu tiên được tạo có chứa tham chiếu đến các đối số và biến cục bộ củawhile [queue.waitForMessage[]] { queue.processNextMessage[]; }
8while [queue.waitForMessage[]] { queue.processNextMessage[]; }
- Khi
8 gọiwhile [queue.waitForMessage[]] { queue.processNextMessage[]; }
1, khung thứ hai được tạo và đẩy lên trên khung đầu tiên, chứa tham chiếu đến các đối số và biến cục bộ củawhile [queue.waitForMessage[]] { queue.processNextMessage[]; }
1while [queue.waitForMessage[]] { queue.processNextMessage[]; }
- Khi
1 trả về, phần tử khung trên cùng được bật ra khỏi ngăn xếp [chỉ để lại khung gọi củawhile [queue.waitForMessage[]] { queue.processNextMessage[]; }
8]while [queue.waitForMessage[]] { queue.processNextMessage[]; }
- Khi
8 trở lại, ngăn xếp trốngwhile [queue.waitForMessage[]] { queue.processNextMessage[]; }
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ỳ hàm lồng nhau nào sau khi hàm bên ngoài của chúng đã trả về
đố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 [hầu hết không có cấu trúc]
Xếp hàng
Thời gian chạy JavaScript sử dụng hàng đợi tin nhắn, là danh sách các tin nhắn sẽ đượ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 thời điểm nào đó trong vòng lặp sự kiện, bộ thực thi bắt đầu xử lý các thông báo trên hàng đợi, bắt đầu với thông báo cũ nhất. Để làm như vậy, thông báo sẽ bị 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 là tham số đầu vào. Như thường lệ, việc gọi một hàm sẽ tạo một khung ngăn xếp mới để sử dụng hàm đó
Quá trình xử lý các chức năng tiếp tục cho đến khi ngăn xếp trống một lần nữa. 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ó]
vòng lặp sự kiện
Vòng lặp sự kiện có tên như vậy vì cách nó thường được triển khai, thường giống như
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
2 chờ một tin nhắn đến một cách đồng bộ [nếu một tin nhắn chưa có sẵn và đang chờ xử lý]"Chạy đến khi hoàn thành"
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 hay khi lập luận về chương trình của bạn, bao gồm thực tế là bất cứ khi nào một chức năng 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ác]. Điều này khác với C, chẳng hạn, ở chỗ nếu một hàm chạy trong một luồng, thì nó có thể bị dừng tại bất kỳ thời đ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
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 bằng hộp thoại "tập lệnh mất quá nhiều thời gian để chạy". Một thực hành tốt để làm theo là làm cho quá trình xử lý tin nhắn trở nên ngắn gọn và nếu có thể, hãy cắt một tin nhắn thành nhiều tin nhắn
Thêm tin nhắn
Trong trình duyệt web, các thông báo được thêm vào bất cứ khi nào một sự kiện xảy ra và có một trình xử lý sự kiện được đính kèm với nó. Nếu không có người nghe, sự kiện sẽ bị mất. Vì vậy, một lần nhấp vào một phần tử có trình xử lý sự kiện nhấp chuột 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à một 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ó tin nhắn nào khác trong hàng đợi và ngăn xếp trống, tin nhắn sẽ được xử lý ngay sau khi trì hoãn. Tuy nhiên, nếu có các tin nhắn, tin nhắn while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 sẽ phải chờ các tin nhắn khác xử lý. Vì lý do này, đối số thứ hai chỉ ra thời gian tối thiểu — không phải là thời gian đảm bảoĐây là một ví dụ thể hiện khái niệm này [
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 không chạy ngay sau khi hết giờ]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ễ
Độ trễ bằng 0 không có nghĩa là cuộc gọi lại sẽ tắt sau 0 mili giây. Gọi
while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 với độ trễ của 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 đã choViệc thực thi phụ thuộc vào số lượng tác vụ đang chờ trong hàng đợi. Trong ví dụ bên dưới, 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 hàm gọi lại được xử lý, vì độ trễ là thời gian tối thiểu cần thiết để bộ thực thi xử lý yêu cầu [không phải là thời gian đảm bảo]while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 cần đợi tất cả mã cho các tin nhắn được xếp hàng đợi hoàn tất mặc dù bạn đã chỉ định giới hạn thời gian cụ thể cho while [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3 của mìnhwhile [queue.waitForMessage[]] {
queue.processNextMessage[];
}
3Một số thời gian chạy giao tiếp với nhau
Một nhân viên web hoặc một
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ó nguồn gốc chéo có hàng đợi ngăn xếp, đống và tin nhắn riêng. Hai thời gian chạy riêng biệt chỉ có thể giao tiếp thông qua việc gửi tin nhắn 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 pháp này thêm một thông báo vào thời gian chạy khác nếu thời gian chạy 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;
}
}
4khô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ờ chặn. Việc xử lý I/O thường được thực hiện thông qua các sự kiện và lệnh 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ề, ứng dụng 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ệ kế thừa 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ên tránh chúng. coi chừng. ngoại lệ đối với ngoại lệ tồn tại [nhưng thường là lỗi triển khai, chứ không phải bất kỳ lỗi nào khác]