for循环,定时器与闭包

首先来看一个常见的问题:

for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
    	console.log( i );
    }, i*1000 );
}
正常情况下,我们期望这段代码以每秒一个的频率分别输出数字1~5。但实际上,这段代码在运行时会以每秒一次的频率输出五次6。

那问题来了,为什么以每秒一次的频率输出五次6,而不是我们期望的1到5呢?

首先,我们都知道在ES6出现之前,js的作用域只有两种:顶层作用域和函数作用域。所以我们在for循环头部定义的变量 i 其实是绑定在全局作用域中的。所以每一次循环,变量 i 的值都会发生改变,而循环内的console.log(i),里面的i指向的就是全局的i 。所以原代码等价于:

var i;
for (i=1; i<=5; i++) {
    setTimeout( function timer() {
    	console.log( i );
    }, i*1000 );
}
再是因为js中函数的参数都是按值传递的 ,所以每一次for循环时,都创建了一个 i 的副本,并把 i 的值传入setTimeout函数中的参数 i*1000 中去。所以结果才会是以每秒一次的频率输出数字。

那为什么输出的是6,而不是我们期望的1到5呢?想知道答案就得了解setTimeout函数的运行机制。

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。setTimeout的延迟不是绝对精确的,它只是延迟一段时间后把该函数添加到执行队列当中,并不是在指定的毫秒数后立即执行。所以说如果指定的时间已经过去,但当前队列存在没有运行完的代码,该函数会等到排在队列中所有的函数运行完毕之后才会运行。也就是说所有传递给setTimeout的回调方法都会在整个环境下的所有代码运行完毕之后执行。

所以说,在执行setTimeout的回调时,for循环已经执行完毕,此刻的 i 的值为6,所以就算把上面代码的延迟时间改为0,也只会立即输出五个6。如:

for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
    	console.log( i );
    }, 0 );
}


那如果想得让 代码以每秒一个的频率分别输出数字1~5,该怎么去改造呢?

第一种方法:利用js中参数传递是按值传递的特征,可以简单的改造成下面的代码:

var output = function(i){
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
};

for (var i=1; i<=5; i++) {
	output(i);
}

第二种方法:用立即执行函数表达式来创建作用域并传入参数

在迭代内使用IIFE 会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。如:

for (var i=1; i<=5; i++) {
	(function(j) {
		setTimeout( function timer() {
			console.log( j );
		}, i*1000 );
	})(i);
}


第三种方法:使用 ES6 let关键字

ES6 新增了let命令,用来声明变量。它所声明的变量,只在let命令所在的代码块内有效。而且let 声明的变量在循环过程中不止被声明一次,而是每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

所以可以简单的写成下面这样:

for (let i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}

还有一点需要注意的是,在for循环中设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。如下面代码会立即输出五个0。

for (let i=1; i<=5; i++) {
	let i = 0;
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}


有关这个问题的其他知识点就不在展开写了,希望能给大家带来帮助。









  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值