今天在做题的时候,遇到了一个不太懂的问题,经过查阅资料,终于搞明白了这个问题,发出来当做个笔记。
首先是var:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
})
};
输出结果:
let方法:
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
})
};
输出结果:
可以看到,var方法输出结果为5个5,let方法输出结果为01234,那么为什么会出现这种情况呢,主要原因就是因为setTimeout是异步执行的。
首先我们来了解下let和var的区别:
然后这道题涉及到了异步,作用域还有闭包。
在var里面:
setTimeout是异步执⾏,10ms后往任务队列⾥⾯添加⼀个任务,只有主线线上的全部执⾏完,才会执⾏任务队列⾥的任务,当主线执⾏完成后,i是5,所以此时再去执⾏任务队列⾥的任务时,i全部是5了。对于打印5次是:
每⼀次for循环的时候,settimeout都执⾏⼀次,但是⾥⾯的函数没有被执⾏,⽽是被放到了任务队列⾥⾯,等待执⾏,for循环了5次,就放了5次,当主线程执行完成后,才进入任务队列里面执行。
在let里面:
因为for循环头部的let不仅将i绑定到for循环中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过var定义的变量是无法传入到这个函数执行域中的,通过使用let来声明块变量能作用于这个块,所以function就能使用i这个变量了。
注意:
由于var命令的变量提升机制,var命令实际只会执行一次,而let命令不存在变量提升,所以每次循环都会执行一次,声明一个新变量(但初始化的值不同),for的每次循环都是不同的块级作用域,let声明的变量也是块级作用域,所以也不存在重复声明的问题,let声明变量的for循环里,每个匿名函数实际上引用的都是一个新的变量。
当然,如果要使用var当做循环循环头的话,出现循环后打印出的结果一模一样的问题,可以使用闭包来解决。
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function timer() {
console.log(i);
});
})(i);
}
输出结果:
通过闭包,将i的变量驻留在内存中,当输出i时,引用的是外部函数的变量值i,i的值是根据循环来的,执行setTimeout时已经确定了里面的的输出了。