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);
}

结果如下
在这里插入图片描述

如果对你有帮助,可以👍+关注,我们一起学前端😁
以上均为原创,如有转载请注明来源。

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值