JS中在循环内实现休眠(for循环内setTimeout延时问题)

本文探讨了JavaScript中setTimeout的问题,解释了单线程机制导致的定时器输出始终为最后一次循环变量的原因,并提供了三种解决方案:使用立即执行函数、改用let声明变量和自定义休眠函数。

问题描述:

想要在js中用setTimeout实现这么一个功能:每隔一秒输出一个数字。我们的js代码大概是这样的:

for(var i = 0; i < 3; i++) {
	setTimeout(function () {
		console.log(i);
	}, 1000);
};

运行这段代码会发现,程序在1秒后输出了3个3。(不但没有每隔一秒输出,而且输出的数字还全都是3)

原因分析:

这跟js的阻塞机制有关。
js阻塞机制,跟js引擎的单线程处理方式有关,每个window一个js线程。所谓单线程,即在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。
由于浏览器是事件驱动的(Event driven),因此浏览器中很多行为是异步(Asynchronized)的,很容易有事件被同时或者连续触发。当异步事件发生时,会创建事件并放入执行队列中,等待当前代码执行完成之后再执行这些代码,如鼠标点击事件发生、定时器触发事件发生、XMLHttpRequest完成回调这些事件,都会被放入执行队列中等待。

因此在上述的代码中,setTimeout这个定时器需要等待for循环执行完成,而for循环执行完成了之后,i已经是3了,此时才开始执行setTimeout,因此console.log(i)会是3。

我以为的程序执行过程:
预想的程序执行流程
实际的程序执行过程:
实际的程序执行流程

解决方案:

方案1

在setTimeout外部包装一个立即执行的匿名函数,将for循环的i作为参数传入进去,使得延迟函数的回调可以将新的作用域封闭在每次迭代的内部。

for (var i = 0; i < 3; i++) {
    (function (time, data) {   // 匿名函数的形参
        setTimeout(function () {
            console.log(`这是第 ${time} 次,这是其他参数:${data}`);
        }, 1000 * time);	// 还是每秒执行一次,不是累加的
    })(i, '其他参数')   // 实参,这里把要用的参数传进去
}

作用域问题
在js中,一个{}就是一个代码块,我们在for循环中定义的i变量,全局可以使用,循环中的每一次给i赋值,都是给全局变量i赋值,每次循环都会产生一个代码块,每个代码块中的都是一个新的变量

方案2

将var改为let

for (let i = 0; i < 3; i++) {
   setTimeout(() => {
	  console.log(i);
   }, 1000 * i)
}

let的作用域是块作用域,每次js把setTimeout放到队列的同时,let定义的 i 的值也会跟随setTimeout进去队列。所以每次循环后队列里的setTimeout里的let i 的值是不一样的。而var定义的 i 作用域是全局的,当程序运行到setTimeout时获取的全局var i 的值已经变成3了。

方案3

不使用setTimeout,自己写一个“休眠”函数。

function sleep(delay) {
    var start = (new Date()).getTime();
    while((new Date()).getTime() - start < delay) {
        continue;
    }
}

for(var i = 0; i < 3; i++) {
	//调用自己写的sleep函数
	sleep(1000);
	console.log(i);
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值