for循环中setTimeout的执行问题

文章详细阐述了JavaScript的单线程环境和异步执行机制,特别是setTimeout在for循环中的行为。通过四个题目,解释了由于同步与异步执行顺序以及作用域的影响,导致不同的打印结果。重点讨论了函数表达式、字符串处理、块级作用域(let关键字)以及立即执行函数在这些场景下的作用。
摘要由CSDN通过智能技术生成

for循环中setTimeout的执行问题

有几种情况,看到可能就第一反应就知道是什么,但是还不一定对,首先我们先回顾一下js单线程和异步调用的知识

JS是单线程环境,也就是说代码的执行是从上到下,依次执行。也就是同一个时间只能做一件事。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript将所有任务分成两种,一种是同步任务,另一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

在所有同步任务执行完之前,任何的异步任务是不会执行的。 而setTimeout就是一个异步任务,所以会先执行for循环这个同步任务,把setTimeout()放进任务队列中等待主线程的for循环执行完毕,一旦"执行栈"中的所有同步任务执行完毕(循环结束后此时i=10)就会从队列中取出setTimeout()
for循环一次碰到一个 setTimeout(),并不是马上把setTimeout()拿到异步队列中,而要等到一秒后,才将其放到任务队列里面。

异步执行的运行机制如下:

所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的第三步。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会循环反复。

以下四种是会放入到异步队列中

  1. setTimeout和setlnterval
  2. DOM事件
  3. ES6中的Promise
  4. Ajax异步请求

题目一

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

冷不丁一想,看着就是十个10,那就错了,答案是0、1、2、3、4、5、6、7、8、9;
我们分析一下这个问题,看setTimeout里面的第一个参数,并不是一个正常的函数,这个函数你可以理解为下面的这个函数,是一个匿名自调用函数,也就是一个匿名立即执行函数(IIFE),console.log()和console.log是不一样的,前者是立即执行,后者不是,后者只是个函数名,该题目是for循环执行的时候console.log(i)是一起同步执行的,setTimeout是后面异步队列中执行的,所以会直接打印出0、1、2、3、4、5、6、7、8、9。

(function() {
	console.log(1)
})()

题目二

这个题目,会直接打印出十个10,原因在于加了双引号的console.log()不再是立即执行函数。setTimeout() 会判断第一个参数是否是[function],如果不是,则会尝试将它当做字符串处理。也就是说,console.log(i)执行后的返回值转为字符串。

 for(var i=0;i<10;i++){
     setTimeout("console.log(i)",1000);//连续的10个10
 }

题目三

这个问题答案和题目二是一样的,但是机制是不一样的,这个实际上是for循环先执行完,然后将setTimeout放入任务队列中,然后执行setTimeout中的代码,此时for中的i已经变成了10,所以会打印十次10.

 for(var i=0;i<10;i++){
     setTimeout(function(){
         console.log(i);  //连续的10个10
     },0);
 }

题目四

我们看一下这两段代码,实际上结果是相同的

  1. 第一段代码是因为let关键字,let会创建一个块级作用域,也就是说在for循环中,let的i,会生成十个不同的i,每一个不受另外的i的影响,所以setTimeout异步队列后执行的时候,打印的每个i都是不同的,所以就会打印出0到9
  2. 第二段我们可以看出实际上是es5的实现块级作用域的方法,也就是创建一个和题目一一样的立即执行函数,立即执行函数中就会马上打印出for循环每一次循环的i的值
for(let i=0;i<10;i++){
     setTimeout(function(){
         console.log(i);  //0到9
     },0);
}
var func = [];
for(var i = 0; i < 10; i++) {
  func.push((function(value){
    return function() {
      console.log(value)// 0-9
    }
  })(i));
}
func.forEach((func) => {
  func();
})

总结

这几个题目基本上把所有的for循环中嵌套setTimeout,和let关键字所能考察的知识都概括进去了,可以多总结,多看几遍,就能深入理解其中的含义了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值