当前位置:网站首页>对for(var i = 0;i < 5;i++) {setTimeout(() => console.log(i),1000)}的深入分析
对for(var i = 0;i < 5;i++) {setTimeout(() => console.log(i),1000)}的深入分析
2022-07-05 05:47:00 【weixin_41387874】
一个常见的问题
for(var i = 0;i < 5;i++)
{
setTimeout(() => console.log(i),1000);
}
对于这个问题,想必大家都有所耳闻,最终输出是
5 5 5 5 5
毫无疑问,这是和我们的预期输出不符的(预期输出是0 1 2 3 4)。造成这个问题的原因,就是var关键字声明的标识符的作用域范围是函数作用域。
因此在上面的js代码中,i标识符是存放在全局作用域中的。因此,当setTimeout的回调函数执行的时候,i标识符存放的值已经是5了(5是循环结束的终点),因此将输出5个5。
但这里还有一个疑点,为什么setTimeout执行的时候i已经是5了,是因为等待了1秒钟嘛?
setTimeout的执行时机
现在我们知道了第一个问题的答案和造成错误的原因,那么下面这个问题呢
for(var i = 0;i < 5;i++)
{
setTimeout(() => console.log(i),0);
}
实际上这个问题的输出也是
5 5 5 5 5
这个输出实际上告诉了我们刚刚疑点的答案,并不是因为等待了1秒钟导致setTimeout执行的时候i中存放的值变成了5。
那么是什么原因呢?
我们可以仔细阅读一下这个代码,我们发现for循环是同步代码,而setTimeout是异步代码。
原来如此,我们立刻联想到事件循环,事件循环可以帮助我们区分同步代码和异步代码的执行时机。事件循环简单来讲是先执行宏任务,再清空微任务队列的一个循环。
其中常见的宏任务有
- <script>标签下所有同步代码
- setTimeout、setInterval
- I/O
- UI交互
常见的微任务有
- Promise相关方法
因此js引擎在执行这个for循环的时候,遇到了setTimeout,将会将setTimeout放入宏任务队列,之后接着执行同步代码,当同步代码执行完毕后去检查微任务队列,在下一轮事件循环开始的时候才会将setTimeout从宏任务队列中取出。
因此,setTimeout的执行必然在所有同步代码,也就是整个for循环执行完毕之后,因此在setTimeout执行的时候,i已经是5了。
解决方法
现在我们明确了造成代码输出和预期不符的原因,也就是作用域。
首先很容易想到,如果把标识符i限制在块作用域内,就可以解决问题。
使用let
for(let i = 0;i < 5;i++)
{
setTimeout(() => console.log(i),1000);
}
setTimeout的回调函数() => console.log(i)的词法作用域是for循环内部的块作用域。而这个函数被指定为回调函数,毫无疑问,将会在其词法作用域以外被调用,并且当它被调用的时候js引擎会对i进行RHS引用,因此即使for循环已经执行完毕,其每一层循环的作用域依旧被() => console.log(i)维持,因此setTimeout在执行的时候可以访问到每一层循环的i。这实际上形成了我们常说的闭包(一个函数在其被定义的词法作用域以外被调用,并且维持着对其被定义的词法作用域中变量的引用)。
使用IIFE
IIFE是Immediately Invoked Function Expression,立即调用函数表达式。
for(var i = 0;i < 5;i++)
{
setTimeout((function(i){
return () => console.log(i);
})(i),1000)
}
这里使用IIFE的目的是使用闭包。每一层循环执行IIFE相当于为每一层循环都创建了一个函数作用域,并把每一层循环的i的值作为参数传入,存放在IIFE的函数作用域的同名标识符中。并且这个IIFE将() => console.log(i)作为值返回给了setTimeout。
同样的,() => console.log(i)被定义在IIFE的函数作用域,它将在它所在的词法作用域以外被调用,并且在() => console.log(i)中维持了对它所在词法作用域中的变量的引用(在这里,实际上词法作用域的作用域链查找中的‘同名遮蔽‘也起了作用)。() => console.log(i)函数在调用时,js引擎会对i进行RHS查找,js引擎首先询问了() => console.log(i)的自身函数作用域,发现并没有这个标识符,于是根据词法作用域查找规则,去() => console.log(i)的上一级词法作用域,也就是IIFE的函数作用域查找标识符i,在这里js引擎发现了标识符i,因此有这个闭包存在(对IIFE作用域的引用),setTimeout在执行的时候可以访问到每一层循环的i。
边栏推荐
- 卷积神经网络——卷积层
- SSH password free login settings and use scripts to SSH login and execute instructions
- 全国中职网络安全B模块之国赛题远程代码执行渗透测试 //PHPstudy的后门漏洞分析
- Daily question 2006 Number of pairs whose absolute value of difference is k
- Corridor and bridge distribution (csp-s-2021-t1) popular problem solution
- Fried chicken nuggets and fifa22
- 2022 极术通讯-Arm 虚拟硬件加速物联网软件开发
- Over fitting and regularization
- wordpress切换页面,域名变回了IP地址
- 智慧工地“水电能耗在线监测系统”
猜你喜欢

1.14 - 流水线

Dynamic planning solution ideas and summary (30000 words)

Solution to game 10 of the personal field

CF1634E Fair Share

Implement a fixed capacity stack

sync. Interpretation of mutex source code

Talking about JVM (frequent interview)

Solution to the palindrome string (Luogu p5041 haoi2009)

剑指 Offer 58 - II. 左旋转字符串

Chapter 6 data flow modeling - after class exercises
随机推荐
shared_ Repeated release heap object of PTR hidden danger
[jailhouse article] look mum, no VM exits
剑指 Offer 53 - I. 在排序数组中查找数字 I
CF1634E Fair Share
Acwing 4301. Truncated sequence
26、 File system API (device sharing between applications; directory and file API)
卷积神经网络简介
[practical skills] how to do a good job in technical training?
PC register
Pointnet++ learning
Time complexity and space complexity
Alu logic operation unit
Control unit
Sword finger offer 53 - ii Missing numbers from 0 to n-1
6. Logistic model
Solution to game 10 of the personal field
游戏商城毕业设计
Some common problems in the assessment of network engineers: WLAN, BGP, switch
Hang wait lock vs spin lock (where both are used)
EOJ 2021.10 E. XOR tree