今天听同学说了个很好玩的面试题,个人觉得很有意思,故在此写点自己的看法。
问题是这样的:当setTimeout在for循环中出现时,在定时器中打印控制for循环的变量,但后来打印的时候,会发现,打印出来的结果是重复了变量满足最后一个条件的值。
话可能说的不是很清楚,我们通过代码来看看到底是怎么回事。在此之前,需要注意的一点是,setTimeout是异步执行的。
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(new Date, i);
}, 1000)
}
console.log("for循环外:", new Date, i)
然后打印的结果是这样的,
很明显,这不符合我们的需求,我们想要实现的应该是每间隔1秒打印从0递增到4的i值。
那么,有什么解决办法吗?还真有,而且,据我查的资料来看,目前有3种方法。
不过,虽然方法不同,但基本上原理差不多,都是改变作用域,把变量私有化。
方法1: 使用闭包
逻辑:如果每次for循环时将 i 的值保存起来,等到定时器里执行console.log(i)的时候再去调用保存好的 i 的值,是不是就可以输出i的不同取值了呢?
基于这种思想,可以使用闭包,形成私有作用域。这时每次for循环传入的 i 的值都将作为私有变量被保存在内存中,等待for循环执行完毕后,跟随任务队列输出。
for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout(function () {
console.log(new Date, j);
}, 1000)
})(i)
}
console.log("for循环外:", new Date, i)
方法2: 封装函数
逻辑:对settimeout进行封装,改变作用域,使得每次调用定时函数都有自己的私有变量值。
for (var i = 0; i < 5; i++) {
fun(i)
}
console.log("for循环外:", new Date, i);
function fun(i) {
setTimeout(function () {
console.log(new Date, i);
}, 1000);
}
方法3: setTimeout() 自身第三个参数的巧妙使用
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,第三个参数是传给执行函数的其他参数。
for (var i = 0; i < 5; i++) {
setTimeout(function (k) {
console.log(new Date, k);
},1000,i)
}
console.log("参数for循环外:", new Date, i)
不成方法的方法: 将 var 换成 let
还可以用let,let是ES6新增用来声明变量的,一般用在声明局部变量中。let声明的变量的范围会生成一个私有作用域,也叫作块级作用域,该变量只会在当前作用域中生效,以 { } 为标识。
所以,如果用let的话,外层的打印的i是未被定义的。
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(new Date, i);
}, 1000)
}
console.log("for循环外:", new Date, i)
以上就是我总结的4种方法,不过let最好不能算作一种方法。
然后,这边还有一些原理性的问题我没有提到,提供两篇大神的博客,仅以参考。
参考1:js经典面试题:setTimeout+for循环组合,使用闭包循环输出1,2,3,4,5(CSDN)
参考2:JS 异步编程六种方案(简书)