for循环内的setTimeout、setInterval(闭包、异步、变量提升)
一、初级for循环内的setTimeout(不清除延时器)
for (var i = 0; i < 4; i++) {
setTimeout(function () {
console.log(i);
}, 1000)
}
// 输出结果:4444
原因:setTimeout是宏任务(异步执行),每次 for 循环内创建的setTimeout任务都在队列中排队等候,等待for循环结束才会执行。var定义的变量 i 会暴露到全局作用域,当for循环结束后 i 已经变成了4,再执行队列中的四个setTimeout就会输出4444。这个比较简单,相信大家都明白。
ES5解决办法:使用立即执行函数将每次的 i 保存(利用了闭包的特性),以达到输出0123的目的,代码如下:
for (var i = 0; i < 4; i++) {
(function (a) {
setTimeout(function () {
console.log(a);
}, 1000);
}(i)); // 将i当作实参传递给a
}
// 输出结果:0123
// 或者这样写:
for (var i = 0; i < 4; i++) {
setTimeout(fn(i), 1000);
function fn (a) {
return function () {
console.log(a);
}
}
}
// 输出结果:0123
ES6解决办法:使用let声明 i,let解决了var声明的内存泄漏、全局污染等问题。let声明将在for循环内形成块级作用域,每一轮for循环的 i 都只在本轮有效。代码如下:
for (let i = 0; i < 4; i++) {
setTimeout(function () {
console.log(i);
}, 1000)
}
// 输出结果:0123
二、谜之for循环内的setTimeout(清除延时器)
for (var i = 0; i < 4; i++) {
var timer = setTimeout(function(i) {
console.log(i);
clearTimeout(timer)
}, 1000, i);
}
// 输出结果:012
小❔,你是否有很多小????~~~~
先是setTimeout参数一懵圈????,为啥三个参数?!简单地说,setTimeout可以有若干参数,除了function和延时以外的参数会在延时结束时传入function。形如:
setTimeout(function(msg1,msg2,...){},1000,'回调参数1','回调参数2',...);
如果对setTimeout还不明白,就点这里吧,如果你早知道了就先给自己鼓个掌????
继续看程序,看到程序执行结果【0 1 2】,又是一懵圈????为啥少个【3】,我是谁,我在哪?????其实是这样的,从setTimeout的第三个参数传递 i 进入到function,形成了闭包,所以可以输出【0 1 2】而不是输出【4 4 4 4】。为什么没有输出【3】,这是因为timer没有形成闭包,每次for循环都会创建timer,执行setTimeout需要等待for循环结束,当for循环结束时timer已经变成了第四次的timer(如果你想验证timer是否是最后一次的timer,你可以再function中输出timer),所以clearTime只清除了最后一次的timer,所以【3】没了。
可能有人会想????,timer也变成参数,进行传递,形成闭包就可以解决问题,代码如下:
for (var i = 0; i < 4; i++) {
var timer = setTimeout(function(i, timer) {
console.log(i);
clearTimeout(timer)
}, 1000, i, timer);
}
// 输出结果:0123
看似解决了问题,但实际上是有问题存在的。我们对代码稍作修改,再setTimeout内部打印timer,console.log(i);
改为console.log('i的值:' + i, "timer的值:" + timer);
运行结果如下图:
造成这样问题的原因有两点:????首先是var定义变量有变量提升,其值为undefined;其次是赋值运算符是自右向左执行的。
以上代码的执行过程:for 循环第一圈执行时,由于赋值表达式自右向左执行,所以先执行setTimeout,传值timer的时候因为有变量提升所以不报错,但是值为undefined,之后setTimeout产生标识id,赋值给timer;for 循环第二圈执行时,还是先执行setTimeout,传递的timer是上一次timer的值,就这样依次类推,直到for循环结束,这导致了最后一个延时器没有被清除。
解决方法:当然可以用ES6的let来定义timer,原理和上面介绍的let一样,这里不赘述。代码如下:
for (var i = 0; i < 4; i++) {
let timer = setTimeout(function(i) {
console.log(i);
clearTimeout(timer)
}, 1000, i);
}
// 输出结果:0123
三、初级for循环内的setInterval(不清除定时器)
for (var i = 0; i < 4; i++) {
setInterval(function () {
console.log(i);
}, 1000)
}
// 输出结果:444444444444....(无限循环)
原因:setInterval和setTimeout类似,都是宏任务(异步执行),每次 for 循环内创建的setInterval任务都在队列中排队等候,等待for循环结束才会执行。var 定义的变量 i 会暴露到全局作用域,当 for 循环结束后 i 已经变成了4,再执行队列中的四个setInterval就会输出4444(无限次)。这个比较简单,相信大家都明白。
ES5解决办法:使用立即执行函数将每次的 i 保存(利用了闭包的特性),以达到输出0123(无限次)的目的,代码如下:
for (var i = 0; i < 4; i++) {
(function (a) {
setInterval(function () {
console.log(a);
}, 1000);
}(i)); // 将i当作实参传递给a
}
// 输出结果:012301230123...(无限重复)
// 或者这样写:
for (var i = 0; i < 4; i++) {
setInterval(fn(i), 1000);
function fn (a) {
return function () {
console.log(a);
}
}
}
// 输出结果:012301230123...(无限重复)
ES6解决办法:使用let声明 i,let解决了var声明的内存泄漏、全局污染等问题。let声明将在for循环内形成块级作用域,每一轮for循环的 i 都只在本轮有效。代码如下:
for (let i = 0; i < 4; i++) {
setInterval(function () {
console.log(i);
}, 1000)
}
// 输出结果:012301230123...(无限重复)
四、谜之for循环内的setInterval(清除定时器)
for (var i = 0; i < 4; i++) {
var timer = setInterval(function(i) {
console.log(i);
clearInterval(timer)
}, 1000, i);
}
// 输出结果:012012012...(无限循环)
这完全和之前setTimeout的原因相同,包括传参也是完全相同的,如下:
setInterval(function(msg1,msg2,...){},1000,'回调参数1','回调参数2',...);
同样不可以用下面这种闭包思想解决问题,因为会导致最后一个定时器没被清除,致使不断输出最后一个 i ,代码如下:
for (var i = 0; i < 4; i++) {
var timer = setInterval(function (i, timer) {
console.log('i的值:' + i, "timer的值:" + timer);
clearInterval(timer)
}, 1000, i, timer);
}
结果如下:
造成的原因也和之前讲解的setTimeout完全相同,只是因为setInterval会不断执行,所以才会不断输出最后一次的结果。
解决方法:当然可以用ES6的let来定义timer,原理和上面介绍的let一样,这里不赘述。代码如下:
for (var i = 0; i < 4; i++) {
let timer = setInterval(function (i) {
console.log('i的值:' + i, "timer的值:" + timer);
clearInterval(timer)
}, 1000, i);
}
结果如下:
如果对你有帮助,可以????+关注,我们一起学前端????
以上均为原创,如有转载请注明来源。
本文地址:https://blog.csdn.net/Kindergarten_Sir/article/details/112260064