When it comes to the running principle of JavaScript, it is natural to avoid the concepts of JS engine, running context, single thread, event loop, event-driven, callback function and so on. This paper mainly refers to article [1,2].
To better understand how JavaScript works, it’s important to understand the following concepts.
- JS Engine
- Runtime (Running context)
- Call Stack
- Event Loop
- Callback
1.JS Engine
To put it simply, THE JS engine is mainly to analyze the lexical and grammar of JS code, and compile the code into executable machine code for the computer to execute through the compiler.
By far the most popular JS engine is V8, the engine that powers Chrome and Node.js. The structure of the engine can be represented simply as follows:
Just like the JVM virtual machine, the JS engine has the concepts of Memory Heap and Call Stack.
-
The stack. The place where the method call is stored and the underlying data type (e.g., var a = 1) are stored in the stack and destroyed when the method call ends (on the stack > after the method call > off the stack).
-
The heap. The memory space allocated to objects in the JS engine is placed in the heap. Var foo = {name: ‘foo’} then the object that foo refers to is stored in the heap.
In addition, JS has the concept of closures, and basic type variables that exist in closures will also be stored in the heap. The details can be seen here 1,3
In the case of closures, it comes down to Captured Variables. We know that Local Variables are the simplest case, stored directly on the stack. Captured Variables are for closures and with,try and catch situations.
function foo () {
var x; // local variables
var y; // Captured variable, bar cites Y
function bar () {
// The context in bar captures the variable y
use(y);
}
return bar;
}
Copy the code
As mentioned above, y is captured variable and stored in the heap because it is captured in a closure with bar().
2.RunTime
JS in the browser can call the browser provided by the API, such as the window object, DOM related API. These interfaces are not provided by the V8 engine, they exist in the browser. So in simple terms, these related external interfaces can be called by JS at RunTime, as well as JS Event loops and Callback queues, called RunTime. Some places also consider the core lib library used by JS as part of the RunTime.
Similarly, in Node.js, you can refer to the apis provided by Node’s various libraries as RunTime. As a result, Chrome and Node.js use the same V8 engine, but operate in different Environments [4].
3.Call Stack
JS is designed to run in a single thread, because JS is mainly used to implement many interactive operations, such as DOM related operations, which can cause complex synchronization problems if multithreaded. Therefore, JS has been single-threaded since its birth, and the main thread is used for interface related rendering operations (why is the main thread, because HTML5 provides a Web Worker, a separate background JS, for processing some time-consuming data operations. Because the relevant DOM and page elements are not modified, there is no impact on page performance), and blocking can cause the browser to freeze.
If a recursive call has no termination condition and is an infinite loop, it will cause the call stack to run out of memory, such as:
function foo() {
foo();
}
foo();
Copy the code
In the example foo calls itself in a loop with no termination condition, and the browser console outputs the stack of calls to the maximum number of calls.
What if the JS thread encounters time-consuming operations, such as reading files or AJAX request operations? JS uses the Callback function to handle this.
For each method Call in the Call Stack, it forms its own Execution Context. See this article for a detailed explanation of the Execution Context
4.Event Loop & Callback
JS asynchronously processes time-consuming tasks through callback. A simple example:
var result = ajax('... ');
console.log(result);
Copy the code
You don’t get the value of result, result is undefined. This is because ajax calls are asynchronous and the current thread does not wait for the ajax request to arrive before executing the console.log statement. Instead, after an Ajax call, the requested action is handed to the callback function, which itself returns immediately. The correct way to write this is:
ajax('... '.function(result) {
console.log(result);
})
Copy the code
Only then can the result returned by the request be correctly printed.
Javascript engines don’t actually provide asynchronous support, which depends on the runtime environment (browser or Node.js).
So, for example, when your JavaScript program makes an Ajax request to fetch some data from the server, You set up the “response” code in a function (the “callback”), and the JS Engine tells the hosting environment: “Hey, I’m going to suspend execution for now, but whenever you finish with that network request, And you have some data, please call this function back.”
The browser is then set up to listen for the response from the network, and when it has something to return to you, it will schedule the callback function to be executed by inserting it into the event loop.
The above two paragraphs, taken from How JavaScript Works, explain in a layman’s way How JS calls callback functions for asynchronous processing.
So what is an Event Loop?
The Event Loop does only one thing, listening on the Call Stack and Callback Queue. When the Call Stack in the Call Stack is empty, the Event Loop places the first Event (the Callback function) in the Callback Queue in the Call Stack and executes it, repeating the process.
An example of setTimeout and the corresponding Event Loop dynamic diagram:
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
Copy the code
SetTimeout has one important point to note, such as the above example to delay the execution of 5s, not strictly 5s, correctly speaking, at least 5s will be executed later. Because the Web API sets a timer of 5 seconds, the callback function will be added to the queue when the timer expires. At this point, the callback function may not run immediately, because there may be other callback functions added to the queue, and it must wait until the Call Stack is empty before a callback is fetched from the queue.
So the common practice of setTimeout(callback, 0) is to run the callback function immediately after the regular call introduction.
console.log('Hi');
setTimeout(function() {
console.log('callback');
}, 0);
console.log('Bye');
/ / output
// Hi
// Bye
// callback
Copy the code
Here’s an error-prone chestnut:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
}
// Output: 5 5 5 5 5 5
Copy the code
The top chestnut does not output 0,1,2,3,4, as the first reaction would suggest. But after combing the JS time loop, it should be easy to understand.
For (var I = 0; i < 5; i++) {… } method, inside the timer will run out of time will directly put the callback function into the event queue, wait for the completion of the for loop, then put in the call stack. By the time the for loop completes, the value of I has changed to 5, so the final output is all 5.
Check out this interesting article about timers
Finally, you can refer to this video for Event Loop. So far, the event loop is the event loop in the front-end browser. For details about Nodejs event loop, please refer to my other article node.js Design Pattern: Reactor (Event Loop). For the difference and comparison between the two, you can check the Event Loop that you do not know in this article, and summarize and compare the two kinds of Event Loop.
conclusion
Finally, to sum up, JS operation principle mainly has the following aspects:
-
The JS engine is mainly responsible for converting JS code into machine code that can be executed by the machine, while some WEB apis called in JS code are provided by its runtime environment, in this case the browser.
-
JS runs in a single thread, pulling code off the call stack each time. If the current code is very time consuming, it blocks the current thread and causes the browser to stall.
-
The callback function is called by joining the Event queue and waiting for the Event Loop to pick it up and put it on the call stack. Only when the Event Loop hears that the call stack is empty will it remove the callback function from the queue head and put it in the call stack.
Main reference
1.How JavaScript works: an overview of the engine, the runtime, and the call stack
2.How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await
3.Philip Roberts: What the heck is the event loop anyway?