这是一道很经典的面试题,其中确是会让写了很多年js代码的人都会感到困扰。
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
执行结果:在1s后同时输出5个5
那么其中是不是有很多想不明白的问题呢
思考1:为什么 console.log的结果是5 ,而不是4?
答案1:如果你对这个问题感到疑惑的话,那么你就要弄明白for循环的执行顺序
for (var i = 0; i < 5; i++) {}
①首先执行计时器变量var i = 0,且在这个循环中只执行一次
②循环开始前,用i判断是否i<5成立,如果为真,进入循环体,如果为假,跳出循环体
③执行i++(无论是否条件判断成立,都会执行这一步)
④更新计时器,重复②③
具体步骤
for (var i = 0; i < 5; i++) {}
1.var i=0
2.0<5 成立 执行循环体
3.执行i++,更新i,i=1
4.1<5 成立 执行循环体
5.执行i++,更新i,i=2
6.2<5 成立 执行循环体
7.执行i++,更新i,i=3
8.3<5 成立 执行循环体
9.执行i++,更新i,i=4
10.4<5 成立 执行循环体
11.执行i++,更新i,i=5
12.5<5 不成立 跳出循环体,循环结束
所以,我们最后得到的i其实是5
思考2:为什么定时器是同时在1s后输出,而不是间隔1s输出?
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
注意,后面的5个定时任务都是在1s后同时执行,所以不存在间隔1s的情况
思考3:为什么console.log的结果不是0,1,2,3,4
这个问题你需要知道以下两点
1.他的执行步骤是先触发同步任务,然后在执行异步任务
2.es3的语法var并没有块结构的说法
for循环就是同步任务,定时器就是异步任务
所以我们可以将这个面试题目进行解析:
for (var i = 0; i < 5; i++) {}
i = 5
在进入循环前,i早已经赋值成为5,
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
setTimeout(function() {
console.log(i);
}, 1000);
如果想要达到输出0,1,2,3,4的效果,我们可以将var换成let
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
理解一下来自《JavaScript高级程序设计(第4版)》的这句话,JS引擎会为for
循环中的let
声明分别创建独立的变量实例,即
使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。并且是在块中声明的,他的运行过程就可以是
{
let i=0;
setTimeout(function() {
console.log(i);
}, 1000);
}
{
let i=1;
setTimeout(function() {
console.log(i);
}, 1000);
}
{
let i=2;
setTimeout(function() {
console.log(i);
}, 1000);
}
{
let i=3;
setTimeout(function() {
console.log(i);
}, 1000);
}
{
let i=4;
setTimeout(function() {
console.log(i);
}, 1000);
}
即在let中,每个i都是指向当时创建的新变量,0,1,2,3,4