先看一段代码

for (var i=0; i<10; i++) { setTimeout(console.log(i), i*1000 );}
答案:0、1、2、3、4、5、6、7、8、9

再比较一段代码

for(var i=0;i<10;i++){setTimeout(function(){console.log(i);  },i*1000);}
答案:10、10、10、10、10、10、10、10、10、10

发现什么奇怪的问题了吗?
为什么第二段代码的结果是10个10呢?

原因是for循环中使用setTimeout涉及到了异步机制。说到异步机制那就要说一下JS的运行机制

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

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

一般来说,有以下四种会放入异步任务队列:
setTimeout和setlnterval
DOM事件
ES6中的Promise
Ajax异步请求

回到开头的代码中:

for(var i=0;i<10;i++){setTimeout(function(){console.log(i);  },i*1000);}
答案:10、10、10、10、10、10、10、10、10、10

代码中的for循环是个同步任务,setTimeout是个异步任务,所以把for循环执行完毕后(i=10)才会执行setTimeout()函数,所以导致输出了10个10的结果。

具体执行步骤如下:
1.for循环为同步任务,先执行产生了十个定时器。
(由于这里的for循环不是独立的代码块,声明的 i 为全局变量,遍历完成相当于全局环境i=10;)
2.同步任务for遍历完了再执行异步任务的定时器。
(此时定时器运行环境与for循环同级,即在全局环境i=10下运行,因此输出10个10)

那么怎么才能输出正确答案0、1、2、3、4、5、6、7、8、9而不是连续的10个10呢?
主流的方法是用Js的闭包来实现这个问题:因为闭包里setTimeout()并不是暴露在全局环境下,而是在闭包的独立作用域中。(这种方法当然是可行的,且兼容几乎所有浏览器,但是闭包写起来有点繁琐…有没有更简单的方法呢?答案是~有的!)

回到我们的问题结果出现本身:问题代码里for循环不是独立的代码块,导致了所声明的i为全局变量,进而导致了定时器执行时候的i为遍历后的i=10。

解决方法:将变量i改为let声明的变量而不是全局变量,此时的for循环就会变成是一个单独的作用域,作用与闭包形成的块级作用将相同,因此能够输出想要的结果。

for(let i=0;i<10;i++){setTimeout(function(){console.log(i);  },i*1000);}
答案:0、1、2、3、4、5、6、7、8、9

温馨提示:ES6中的属性方法let目前只支持现代浏览器,所以如果考虑浏览器兼容问题的小伙伴还是乖乖用闭包的方法吧。

那么又有小伙伴有疑问了,既然在for循环中运行setTimeout()涉及到了异步,那为什么第一段代码的结果不是连续的10个10呢?

这就涉及到了console.log 与console.log()的区别了。
先用eval()测试一下:console.log 与console.log()

此处顺便一提eval()函数的用法:eval(str),此函数可以接受一个字符串str作为参数,并把此str当做一段javascript代码去执行,如果str执行结果是一个值则返回此值,否则返回undefined。

再来看一眼开头的两段代码:

for (var i=0; i<10; i++) { setTimeout(console.log(i), i*1000 );}答案:0、1、2、3、4、5、6、7、8、9
for(var i=0;i<10;i++){setTimeout(function(){console.log(i);  },i*1000);}
答案:10、10、10、10、10、10、10、10、10、10

参考的博客里对console.log 与console.log()的区别是这么写的:
console.log是一个function 函数名,而console.log()是立即执行的,也就是IIFE,所以console.log()是同步任务跟for循环是同步执行的,而setTimeout()是异步任务需要等到主线程的同步任务执行完毕后才能执行。(看完之后我还是不太理解,于是乎又针对这个问题再次搜索了一番)

海量查阅后自我总结如下:
针对上面两端代码,每段代码的每次for循环其实都会执行setTimeout(),setTimeout() 会判断第一个参数是否是[function],如果是,则执行异步任务。如果不是,则会尝试将它当做字符串处理。所以第二段代码setTimeout里面的function被当成异步任务不会立刻执行,而是被放入任务队列。而第一段代码中console.log(i)被当作字符串处理(即setTimeout里没有回调函数),所以相当于是跟着for循环同步执行的而不需要等到for都遍历完成(i=10)再执行计数器。

再来对比第三段代码:

 for(var i=0;i<10;i++){setTimeout("console.log(i)",1000);}答案:10、10、10、10、10、10、10、10、10、10

与第一段代码比较下:

for (var i=0; i<10; i++) { setTimeout(console.log(i), i*1000 );}答案:0、1、2、3、4、5、6、7、8、9

发现什么没有?console.log(i)上加双引号,结果就变成10、10、10、10、10、10、10、10、10、10。

其实弄清楚了上文所说到的例子就很容易理解,加了双引号的console.log()相当于是又变成一个回调函数,而不会被当作字符串处理(当然也就不会立即执行函数),所以就被当成异步任务处理。

自此花了大半天才弄清楚的问题,分享出来也希望能帮到大家啦~如果有错欢迎评论区滴滴我!

此篇总结涉及的博客文章实在是多,如有侵权请与本人联系。
总结所参考的资料大部分来自此篇博客
https://blog.csdn.net/Febby_/article/details/94763441

在for循环中运行setTimeout相关推荐

  1. 在for循环中运行setTimeout的三种情况

    在for循环中运行setTimeout是前端开发很常见的一种模式.最近被考了一道相关的题目觉得很有意思也很容易犯错,于是记录下来当做学习笔记. 下面先来看一段代码: for(var i=0;i< ...

  2. ES6中let的用法及一个小案例,在for循环中运行setTimeout的四种情况:

    1.ES6中let和var的主要区别: (1)let声明的变量只在当前块级作用域内有效. if(true){var a=1;let b=2; } console.log(a); console.log ...

  3. 你不知道的三种在for循环中使用setTimeout的方法

    在 for 循环中使用 setTimeout 中可能会出现结果输出不符合预期: for (var i = 0; i < 5; i++) {setTimeout(() => {console ...

  4. 关于for循环中使用setTimeout要注意的几点

    首先了解setTimeout延时器的运行机制 setTimeout(fun,time): 定时器会先将fun函数放到队列中,等待区域内其他主程序执行完毕后按时间先后顺序执行fun函数 因此若是这样使用 ...

  5. for 循环中使用 setTimeout 的问题

    问题引入 下面代码的输出结果不是间隔3秒依次输出 1, 2, 3, 4, 5.而是隔了3秒连续输出6.这是为什么呢? for (var i = 1; i <= 5; i++){     setT ...

  6. for循环中执行setTimeout问题(任务队列的问题)

    for(var i=0;i<8;i++){setTimeout(function () {console.log(i)},0)} 输出了8次8,这跟js的执行顺序和作用域链有关. 规则: 同步优 ...

  7. NodeJS脚本中for循环中加入setTimeout实现每隔一段时间执行操作

    在nodejs中打算实现每隔5s就console一下信息,总共打印10次,于是想到了for循环加setTimeout来实现,可是写出来发现10次在五秒后全部打印出来了,于是开始想办法解决,直接上解决办 ...

  8. for循环中使用settimeout和清除clearTimeout

    type==1是循环添加settimout,type==2是循环终止clearTimeout 第一种,建议 timeOut=[];shutterAnalysis(type){if(type == 1) ...

  9. Mysql过长的Sql语句优化:在循环中调用存储过程运行动态语句

    在批量查询的时候,有时为了提高检索效率,会使用Unique配合IN查询.此时注意IN中不要写子查询,否则时间复杂度会大大增加.不过对于已经确定的检索范围,如果有Unique索引,效率还是可以的. 但有 ...

最新文章

  1. 问题 1049: [编程入门]结构体之时间设计
  2. kafka 创建topic_Kafka试题
  3. mysql 5.7 redo replacation_mysql replace
  4. oracle load select,Oracle数据库的Load详解
  5. 微课|中学生可以这样学Python(例11.3):tkinter通信录管理系统4
  6. LeetCode(783)——二叉搜索树结点最小距离(JavaScript)
  7. angularjs双向绑定_AngularJS隔离范围双向绑定示例
  8. 微软MCITP系列课程(二四)利用组策略部署软件
  9. 湖北师范学院c语言试题题库,2017年湖北师范学院计算机科学与技术学院803数据结构与C语言程序设计考研题库...
  10. 下载zxing的网址
  11. 飞算(SoFlu)软件机器人——人人都是全栈架构师
  12. 二维数据的分形维数分析(C++实现)
  13. android问卷调查论文设计,基于 android问卷调查app开发与设计.pdf
  14. Discuz 模板目录结构注释说明
  15. C语言实现循环缓冲fifo及总结
  16. Knowledge based | 如何获取某类肿瘤中所有已被报道的相关突变基因?
  17. jquery prop 是什么意思,它的使用方法是什么样的?
  18. python利用ElementTree读写xml
  19. 315维权大数据报告:手机爆炸事件仍在发生,国民安全受到严重威胁!
  20. UML和模式应用读书笔记一(做正确的事)

热门文章

  1. Eclipse3.1.2 汉化 过程 中文版
  2. python row函数_Python numpy 常用函数总结
  3. 案例精选:爬取美食杰所有图片批量下载并入库
  4. 百视通与微软共同宣布9月在华发布Xbox One
  5. pythonbyte转int_Python int与byte类型相互转化
  6. unity飞行模拟游戏源码,支持安卓+IOS双端 unity2019 C#语言开发
  7. 华为P8解锁问题:waiting for device解决方法(亲测成功)
  8. Mysql增删改查|SQL语句(史上最全|实战教学)
  9. linux子系统 xfce,在windows 10 子系统linux上运行xfce4
  10. centos7下安装ossec