Running setTimeout in a for loop is a very common pattern for front-end development. Recently, I was tested on a related question and found it very interesting and easy to make mistakes, so I recorded it as a study note.
Let’s look at a piece of code first:
for(var i=0;i<10;i++){
setTimeout(console.log(i),0);
}
after reading this code, think about what the result output is?
is it the first thing that comes to mind is 10 consecutive 10s?
the answer is 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 oh, this is because many people confuse this code with the code below
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i); // 10 in a row
},0);
}
The output of this code is a continuous 10 10.
First, let’s talk about why it’s 10 consecutive 10s. Because using setTimeout in a for loop involves an asynchronous mechanism. Speaking of asynchronous mechanisms, let’s talk about the working mechanism of JS.
JS is a single-threaded environment, which means that the execution of code is executed from top to bottom, sequentially. That is, only one thing can be done at a time.
Single-threading means that all tasks need to be queued, and the previous task ends before the next task is executed. If the previous task took a long time, the latter task had to wait all the time. JavaScript divides all tasks into two types, one is synchronous tasks and the other is asynchronous tasks. Synchronous tasks refer to tasks that are queued on the main thread, and only when the previous task is executed can the latter task be executed; asynchronous tasks refer to tasks that do not enter the main thread but enter the “task queue”, and only the “task queue” notifies the main thread that an asynchronous task can be executed, and the task will enter the main thread for execution.
No asynchronous tasks will be executed until all synchronous tasks have been executed. SetTimeout is an asynchronous task, so the synchronous task of the for loop will be executed first, and the setTimeout() will be put into the task queue to wait for the main thread’s for loop to be executed, and once all the synchronous tasks in the “execution stack” are executed (i=10 at this time after the loop ends), setTimeout()
will be taken out of the queue The for loop hits a setTimeout() at a time, and instead of immediately putting setTimeout() into the asynchronous queue, it waits until a second later before putting it in the task queue.
asynchronous execution works as follows
- all synchronization tasks are executed on the main thread, forming an execution context stack.
- in addition to the main thread, there is a “task queue”. as soon as the asynchronous task has a running result, an event is placed in the task queue.
- once all the synchronization tasks in the “execution stack” have been executed, the system reads the “task queue” to see what events are inside. those corresponding asynchronous tasks, so end the wait state, enter the execution stack, and start execution.
- The main thread keeps repeating the third step above.
- The main thread reads events from the “task queue”, which is a continuous cycle, so the whole operation mechanism is also called event loop. As long as the main thread is empty, it will read the “task queue”, which is how JavaScript works. This process is cyclical.
in general, there are four types that are put into the asynchronous task queue
- setTimeout and setlnterval
- DOM EVENTS
- Promise in ES6
- Ajax asynchronous request
Back to the point, since running setTimeout() in a for loop involves asynchrony, doesn’t the above explanation solve the problem, but why doesn’t the first piece of code result in 10 consecutive 10s? This brings us to the difference between .log console and .log().
Look at the following code:
Do you understand, understand this problem and solve it
In fact, the difference between console .log and console .log () is that the console .log () is executed immediately, that is, IIFE, and the console .log is just a function name. So the console .log () is that the synchronous task is executed synchronously with the for loop, and the setTimeout() is that the asynchronous task needs to wait until the synchronization task of the main thread is executed before it can be executed, so the result is 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
Let’s look at another piece of code
for(var i=0;i<10;i++){
setTimeout("console.log(i)",1000); // 10 in a row
}
Ahaha, the result is a continuous 10 10.
The reason is that the double-quoted console .log () is no longer an immediate execution function. setTimeout() will tell if the first argument is [function], and if not, it will try to treat it as a string. That is, the return value after the console .log (i) is executed is converted to a string.
Now understand the difference between the three cases of running setTimeout() in a for loop
for(var i=0;i<10;i++){
setTimeout(console.log(i),0); // 0、1、2、3、4、5、6、7、8、9
}
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i); // 10 in a row
},0);
}
for(var i=0;i<10;i++){
setTimeout("console.log(i)",1000); // 10 in a row
}