面试:for循环+setTimeout函数

问题

基础知识

setTimeout()方法是window的一个方法,设置一个计时器,一旦计时器到期,该计时器就会执行一个函数或指定的一段代码。

setTimeout(fun|code, [delay], [arg1],[arg2],...);
    fun|code: 一个回调函数或一段代码
    delay:延迟时间(ms)
    arg1,arg2,...: 作为回调函数的参数

setTimeout()允许一个字符串作为第一个参数,而且js内部将会调用eval()函数用来动态执行一段字符串脚本

eval()函数可以接受一个字符串str作为参数,并把此str当做一段javascript代码去执行,
如果str执行结果是一个值则返回此值,否则返回undefined。
如果参数不是一个字符串,则直接返回该参数。

注意:
setTimeout()的第一个参数不管是一个函数还是一段代码,
如果传入的是引用函数地址,则不会立即执行;
如果传入的是执行函数,则立即执行,不受到setTimeout()函数的制约;

情况1:

输出的内容不是0、1、2、3、4、5、6、7、8、9

for(var i = 0; i < 10; i++) {
    setTimeout(() => {
        console.log(i);
    }, 1000);
}
//输出: 1s后连续输出10个10

说明:

  1. 声明变量的var使得i成为一个全局变量,for循环是同步任务
  2. setTimeout是异步执行,1s后往任务队列里面添加一个任务,只有主线上的同步任务执行完,才会执行任务队列里的任务,
  3. for循环每执行一次,setTimeout都会执行一次,但是里面的回调函数() => {console.log(i);}没有被执行,而是被放到任务队列里,等待执行,for循环了10次,回调函数就放入10次
  4. 1s后,当主线任务完成后,i=10,所以此时再去执行任务队列里的任务时,有10个{console.log(i);因此连续输出10个10

情况2:

for(var i = 0; i < 10; i++) {
    setTimeout(console.log(i), 4000);
}
//0,1,2,3,4,5,6,7,8,9

说明:

console.log()是立即执行函数,不是一个函数地址,与for循环都是同步任务,因此会立即执行

情况3:

for(var i = 0; i < 10; i++) {
    setTimeout('console.log(i)', 4000);
}
//输出:4s后连续输出10个10

eval(console.log) => ƒ log() { [native code] }
eval(console.log()) => undefined

说明:
console.log()加上引号以后,就变成了一个引用函数地址,即函数为 console.log,其中的参数为i

解决

1、使用let构建块级作用域

将for循环里的变量i绑定到循环体的每一次迭代中

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

2、使用闭包,构建块级作用域

通过闭包,将i的变量驻留在内存中,当输出j时,引用的是外部函数的变量值i,i的值是根据循环来的,执行setTimeout时已经确定了里面的的输出了。

> 方式一:
	for(var i = 0; i < 10; i++) {
	    ((j) => {
	        setTimeout(() => {
	            console.log(j);
	        }, 1000);
	    })(i);
	}
> 方式二:
	for(var i = 0; i < 10; i++) {
		closure(i);
	}
	function closure(i) {
		setTimeout(() => {
			console.log(i);
		}, 1000);
	}

3、使用立即执行函数

setTimeout里面的第一个参数是立即执行函数,是同步任务,与for循环同步执行,不受setTimeout的制约

//立即执行函数:是一种既不需要函数名同时能够自动执行的函数。
	(function(){
	})();
	
	(function(){
	}());
for(var i = 0; i < 10; i++) {
    setTimeout(((i) => {
        console.log(i);
    })(i), 1000);
}
//setTimeout里面的第一个参数console.log()是IIFE
for(var i = 0; i < 10; i++) {
    setTimeout(console.log(i), 1000);
}

4、使用setTimeout第三个参数

第三个参数:就是给setTimeout第一个函数的参数,即作为回调函数console.log或者(j) => {console.log(j);}的参数传入

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

for(var i = 0; i < 10; i++) {
    setTimeout((j) => {
        console.log(j);
    }, 1000,i);
}

5、利用try/catch

  • catch中的参数会形成一个块级作用域
  • 但是如果在catch内部进行var声明,依然是声明在全局作用域中
for(var i = 0; i < 10; i++) {
    try {
        throw i;
    } catch(i) {
        setTimeout(() => {
            console.log(i);
        }, 1000)
    }
}

6、promise

for(var i = 0; i < 10; i++) {
	Promise.resolve(i).then((i) => {
		setTimeout(() => {
			console.log(i);
		}, 1000);
	});
}

说明:

Promise.resolve() ==> new Promise(() => {resolve()});
resolve是成功状态,后面的then里的参数会使用resolve里的参数,因此相当于形成了一个块级作用域。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值