When learning JS, or when interviewing, will often encounter this classic topic:
for(var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
});
}
console.log('a');
Copy the code
Those familiar with the question can immediately say the answer:
'a'
5
5
5
5
5
Copy the code
The result is to print the string ‘a’, followed by the five digits 5.
Some people will say that this problem is not difficult, and as long as you have encountered this problem, the next time you see the basic will not be wrong, but in fact this simple code contains a lot of JS knowledge.
Here’s a summary.
Single threads, task queues, and Event loops
When you first look at this code, it gives you an illusion:
- It prints five I values in the for loop before it prints the string ‘a’
- Would the for loop print 0,1,2,3,4 instead of some weird 5
However, the actual results are not what we expected because of the js mechanism involved.
Single thread
One of the hallmarks of the JavaScript language is single-threaded, which means you can only do one thing at a time.
Why not allow JS to implement multithreading? Because if you have multiple threads, and one thread creates a div element, and another thread removes it, who should the browser listen to?
So to avoid such conflicting operations, JS is single threaded from the beginning, which is its core feature.
Task queue
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.
If the queue is due to a large amount of computation and the CPU is too busy, it is fine, but many times the CPU is idle because the IO devices (input and output devices) are slow (such as Ajax operations reading data from the network) and have to wait for the results to come out before executing.
The designers of the JavaScript language realized that the main thread could simply ignore the IO device, suspend the pending task and run the next one first. Wait until the IO device returns the result, then go back and continue the pending task.
Thus, all tasks can be divided 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. The task queue notifies the main thread that an asynchronous task is ready to execute.
- 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”. Whenever an asynchronous task has a result, an event is placed in the “task queue”.
- Once all synchronization tasks in the execution stack are completed, the system reads the task queue to see what events are in it. Those corresponding asynchronous tasks then end the wait state, enter the execution stack, and start executing.
- The main thread repeats step 3 above.
Whenever the main thread is empty, it reads the “task queue”, that’s how JavaScript works. The process repeats itself.
In addition to the IO device events, the “task queue” includes some user-generated events (such as mouse clicks, page scrolling, and so on). As long as the callback function is specified, these events are queued up for the main thread to read.
Event loop
The main thread reads events from the “task queue” in a continuous Loop, so the whole operation mechanism is also called an Event Loop.
When the main thread runs, the heap and stack are created, and the code in the stack calls various external apis that add various events (click, load, done) to the “task queue”. As soon as the stack completes, the main thread reads the “task queue” and executes the corresponding callback function for those events.
The timer
With that in mind, let’s go back to this code:
for(var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
});
}
console.log('a');
Copy the code
Why is the timer set to 0 (setTimeout non-write delay is 0 by default)? The timer starts after console.log(‘a’) has run?
In addition to asynchronous operations, timer events are also placed in the JS task queue.
When js code runs to a place with a timer, it puts the timer operation at the end of the task queue and says to it: “You queue first, it’s not your turn, because the synchronization code hasn’t finished yet.”
The synchronization code in question is console.log(‘a’) below.
That is, JS considers setTimeout to be an asynchronous operation that must be queued and can only be executed after the synchronous code has finished executing.
So the reason here is summed up in this sentence:
The timer is not synchronous, it will automatically insert the task queue, wait for the current file all synchronization code and the current task queue all the existing events are completed before execution.
That’s why the string ‘a’ is printed before five 5s.
So why five fives? Why not 0,1,2,3,4?
This is because after all the synchronized code has been executed, the value of I in the for loop has long since changed to 5 and the loop is over. (Note that the parenthesis part of the for loop is also synchronous code.)
That’s why you print five 5’s instead of 0,1,2,3,4.
So you can imagine the code actually running like this to make sense of it:
for(var i = 0; i < 5; i++) { } console.log('a'); SetTimeout (function () {the console. The log (I); }); SetTimeout (function () {the console. The log (I); }); SetTimeout (function () {the console. The log (I); }); SetTimeout (function () {the console. The log (I); }); SetTimeout (function () {the console. The log (I); }); // First loop, I becomes 5, then print a, then print 5 more I // this is just an assumption, easy to understandCopy the code
Scope and closure
This question leads to another question:
What if you want the timer in the for loop to print 0,1,2,3,4 instead of 5 5’s?
The answer is: use immediate execution functions.
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function () {
console.log(i);
});
})(i)
}
console.log('a');
Copy the code
Print result:
'a'
0
1
2
3
4
Copy the code
Why is that?
This is because the I variable defined in the for loop is actually exposed in the global scope, so the anonymous functions in the five timers actually share the same variable in the same scope.
So if you want 0,1,2,3,4, you have to store the current I value separately for each loop.
Using the principle of closures, closures allow a function to continue to access the scope in which it was defined. This newly generated scope saves the current I value for each loop separately.
for(var i = 0; i < 5; I ++) {(function(I) {// This anonymous function generates the closure effect, creating a new scope, which receives the value of I for each loop and saves it even after the loop ends, SetTimeout (function () {console.log(I); }); })(i) }Copy the code
The let keyword, block scope, and try… Catch statement
If you want the timer in the for loop to print 0,1,2,3,4, you can use the ES6 let keyword in addition to closures.
for(let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
});
}
Copy the code
Note that the “for” loop defines “I” instead of “var” and prints “0,1,2,3,4”
What is the question?
Because the let keyword hijacks the block scope of the for loop, it has the effect of a closure. And using let to define loop variables in a for loop has a special effect: each loop redeclares variable I, and each subsequent loop initializes it with the value at the end of the previous loop.
Let can implement block scope effect, but it is ES6 syntax, how to generate block scope in lower version syntax?
Answer: Use try… Catch statement.
Look at the following effect:
for(var i = 0; i < 5; i++) { try { throw(i) } catch(j) { setTimeout(function () { console.log(j); }); }} // prints the result 0,1,2,3,4Copy the code
The magic effect appeared!
This is because try… The curly brace after the catch statement is a block scope, as is the effect of a let. So by throwing the loop variable I in a try block and receiving the passed I in the block scope of the catch, we can save the loop variable to achieve a similar effect to closures and lets.
Ok, that’s all about this interview question.
The original link