Today, while reviewing the advanced use of JavaScript, I found an interesting question. Without further ado, let’s start with the code:
for(var j=0; j<10; j++){ setTimeout(function(){console.log(j)},5000)}Copy the code
Looking at these three lines of code, you might be tempted to say: Closures again? You’re gonna throw up, okay? Now, let’s start by thinking, what is the result of executing this code in the browser?
A: Print 0 through 9 in order?
B: I’ve seen this one before. Print ten tens!
Which answer is correct? Let’s continue with the image above:
- Why print ten tens in a loop instead of zero through nine?
- As a result, setTimeout is executed only after the for loop is out (hence j = 10). Why not execute setTimeout once every iteration?
If you can answer all three of these questions, congratulations, you’re starting to understand JavaScript in depth. If not, read on!
Why are ten tens printed in a loop
Many people tend to answer this question with the result of the second question: “setTimeout was executed after the for loop was out, so ten 10s were printed.” Such an answer can only be said to have dealt with both oneself and others. In fact, to answer the first question, the first question to answer is the second question.
Why not execute setTimeout once for every iteration
As we all know, JavaScript did not have block scope before ES6, which means that the variable j defined by var in the for loop is actually global, that is, it can be accessed globally. In this case, there is only one j in the global scope. Every time the for loop I is updating this j.
The key question now is why the entire for loop is executed before setTimeout, rather than one iteration at a time, as we normally understand.
This brings us to the core feature of JavaScript: single threads.
JavaScript is designed to be used by browsers to interact with users and manipulate the DOM. This is why it has to be single-threaded. Imagine a JavaScript colleague with two threads, one adding content to a DOM node and the other deleting it, and the browser would mess up. So, to avoid complexity, JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change.
Single threading means that all tasks need to be queued until the first one is finished before the next one can be executed. If the first task takes a long time, the second task has to wait forever.
To optimize single-threaded performance, JavaScript divides the task into two types, synchronous and asynchronous. A synchronization task refers to a task that is queued to be executed on the main thread. The next task can be executed only after the first task is completed. Asynchronous tasks are tasks that do not enter the main thread but enter the Task queue. Asynchronous tasks are executed in the task queue only after the synchronous tasks in the main thread are completed. Whenever the main thread is empty, it reads the “task queue”, that’s how JavaScript works. The process repeats itself.
SetTimeout is defined by JavaScript as an asynchronous task. Each iteration of the for loop queues the setTimeout callback to be executed. That is, until the for loop in the synchronous task is completely finished, the main thread will go to the task queue to find the ten setTimeout (ten iterations) callbacks that have not yet been executed and execute them in order (fifO). And at this point, I has gone through the loop and become 10, so what the main thread is doing is doing ten exactly the same callback that prints I, which prints ten 10s. This perfectly answers the first and second questions. The code at the beginning of this article is actually equivalent to the following:
for(var i=0; i<10; i++){} setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
Copy the code
A small setTimeout involves a lot of deep problems in JavaScript. Although an article can be summarized into only a few hundred words, I have consulted a lot of data and conducted many experiments in the process of writing.
Finally, I would like to give you a small but still puzzling problem that I hope interested partners can study together with me:
setTimeout(function(){while(true) {}},6000);
setTimeout(function(){console.log(1)},10000);
setTimeout(function(){console.log(2)},5000);
Copy the code
What is the sequence of execution of the above code? Is the timing of setTimeout executed immediately after the scheduled insertion of the execution stack, or is it executed at the scheduled insertion of the execution stack immediately?
I look forward to your comments.