欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

for循环内的setTimeout、setInterval(闭包、异步、变量提升)

程序员文章站 2022-03-12 22:40:28
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);运行结果如下图:
for循环内的setTimeout、setInterval(闭包、异步、变量提升)
造成这样问题的原因有两点:????首先是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);
}

结果如下
for循环内的setTimeout、setInterval(闭包、异步、变量提升)
造成的原因也和之前讲解的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);
}

结果如下
for循环内的setTimeout、setInterval(闭包、异步、变量提升)

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

本文地址:https://blog.csdn.net/Kindergarten_Sir/article/details/112260064

相关标签: Web前端 前端