Recently, I am searching for more detailed information about JS event handling. Most blogs in China are found to be copied from each other. MDM doesn’t say much about this either. After reading part of the articles, I found that this article is very valuable. Therefore, translation. Although the article was written in 2013, it still has high reference value
The JavaScript Event Loop: Explained
What is the article about?
JavaScript is the most popular scripting language in Web browsers. This article provides you with an overview of the basic event-driven model of the language, which differs from the typical on-demand languages such as Ruby, Python, and Java. In this article, I’ll explain some of the core concepts of the concurrency model in JavaScript, including event loops and message queues, to help you develop a deeper understanding of a language that you’ve already used but still don’t fully understand.
Who is suitable to read this article?
This article is intended for those engineers who have started or are planning to do Web development in the JavaScript language. If you’re already familiar with JavaScript event loops, this article will sound familiar to you. For those of you who don’t know enough about event loops, I hope this article has been helpful so you can better understand the code you face every day.
Non-blocking I/O (I/O)
Almost all I/O in JavaScript is non-blocking. This includes HTTP requests, data access, and reading and writing disks. A single thread handles these operations at run time, provides a callback, and then goes on to do something else. When the operation completes, the message provided by the callback function is pushed to the queue. At some point, the message is removed from the queue and the callback function is triggered.
While this interaction model is already familiar to many developers — such as the handling of mousedown and Click events — it is different from the typical server-side synchronous request handling.
Let’s compare this to making a request to www.google.com and printing the returned code to the console. First, Ruby’s words:
response = Faraday.get 'http://www.google.com'
puts response
puts 'Done! '
Copy the code
The execution path would look something like this:
get
The method is executed, and the thread of execution waits until it receives the response- from
Google
The response is received and returned to the callback and stored in a variable - The value of the variable (the result of the returned response) is output to the console
Done
Output to the console
Let’s do the same thing with Node.js:
request('http://www.google.com'.function(error, response, body) {
console.log(body);
});
console.log('Done! ');
Copy the code
Looks like a significant difference and different behavior:
- The request function is executed, passing an anonymous function as a callback execution function that is executed upon receipt of the response
Done
Is immediately output to the control sleeve- Sometimes, when the response returns, our callback function executes force, and the body of the response is printed out to the console
Event loop
Treating the response to the request as a callback allows JavaScript to do something else before waiting for the asynchronous operation to return successfully and executing the callback. But how are these callbacks executed in memory? What is the order of execution? What causes them to be called?
The JavaScript runtime environment has a message queue for storing messages and for associating callback functions. These messages are arranged in the order in which events are registered (such as mouse click events or HTTP request response events). For example, if a user clicks a button and no callback function for that event is registered, no message is queued.
In a loop, the queue polls for the next message (each poll is treated as a tick), and if there is a message, the corresponding callback is executed.
The call to the callback function is the initial frame of the call stack, and since JavaScript is single-threaded, message polling and processing is stopped until all the callback functions in the stack have returned. Subsequent function calls (synchronous) add new calls to the stack (such as initialize colors).
function init() {
var link = document.getElementById("foo");
link.addEventListener("click".function changeColor() {
this.style.color = "burlywood";
});
}
init();
Copy the code
In this example, when the user clicks on a page element, an onclick event is fired and a message is pushed into the queue. When a message is queued, its callback function changeColor is executed. When changeColor returns (or perhaps throws an exception), the event loop continues. As long as changeColor is specified as the onclick callback, subsequent clicks on that element cause more messages (and those related to changeColor callbacks) to be queued.
Additional messages in the queue
If a function is called asynchronously in your code (such as setTimeout), the callback will be executed later in the event loop as part of another message queue. Such as:
function f() {
console.log("foo");
setTimeout(g, 0);
console.log("baz");
h();
}
function g() {
console.log("bar");
}
function h() {
console.log("blix");
}
f();
Copy the code
Because setTimeout is non-blocking, it is executed after 0 milliseconds and is not processed as part of the message. In this example, setTimeout is called, passing in a callback function g and a timeout event of 0 ms. When the time is up, a message with g as the callback will be queued. The console prints something like: foo, baz, Blix and then prints: bar in the next event loop. If setTimeout is executed twice in the same call frame, pass in the same value. They do it first.
Web Workers
Web workers allow you to move expensive operations into separate threads, saving the main thread from doing other things. Workers have separate message queues, event loops, and separate memory space. The worker communicates with the main thread via a message, which looks a bit like the previous event handling.
First, our worker:
// our worker, which does some CPU-intensive operation
var reportResult = function(e) {
pi = SomeLib.computePiToSpecifiedDecimals(e.data);
postMessage(pi);
};
onmessage = reportResult;
Copy the code
Then there is our js code:
// our main code, in a <script>-tag in our HTML page
var piWorker = new Worker("pi_calculator.js");
var logResult = function(e) {
console.log("PI: " + e.data);
};
piWorker.addEventListener("message", logResult, false);
piWorker.postMessage(100000);
Copy the code
In this example, the main thread spawns and starts a worker, and then adds the logResult callback to the event loop. In worker, reportResult is registered with its own message event. When the worker receives a message from the main thread, the worker returns a message, thus causing reportResult to be executed.
When pushed, a message is pushed to the main thread and pushed onto the message stack (and then the callback function is executed). In this way, developers can delegate CPU-intensive operations to separate threads, freeing up the main thread to continue processing messages and events.
About the closure
JavaScript supports the use of closures in callback functions that maintain access to the environment in which they were created while executing, even after a new call stack is created. It is more interesting to know that the callback is executed in a different message than to know that the callback is created. Consider the following code:
function changeHeaderDeferred() {
var header = document.getElementById("header");
setTimeout(function changeHeader() {
header.style.color = "red";
return false;
}, 100);
return false;
}
changeHeaderDeferred();
Copy the code
In this example, the changeHeaderDeferred execution contains the header variable. SetTimeout is executed, and 100 milliseconds later a message is added to the message queue. The changeHeaderDeferred function returns false, ending the processing – but the header reference remains in the callback function, so it is not garbage collected. When the second message is processed, the function body (changeHeaderDeferred) retains the declaration of the header. After the second processing, the header is garbage collected.