当前位置:网站首页>对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。
边栏推荐
- Sword finger offer 53 - I. find the number I in the sorted array
- Animation scoring data analysis and visualization and it industry recruitment data analysis and visualization
- 1.14 - 流水线
- Daily question - longest substring without repeated characters
- Daily question 1984 Minimum difference in student scores
- 二十六、文件系统API(设备在应用间的共享;目录和文件API)
- One question per day 1447 Simplest fraction
- Wazuh开源主机安全解决方案的简介与使用体验
- Binary search template
- [practical skills] how to do a good job in technical training?
猜你喜欢
1.15 - 输入输出系统
CF1634E Fair Share
Talking about JVM (frequent interview)
Introduction and experience of wazuh open source host security solution
Sword finger offer 53 - ii Missing numbers from 0 to n-1
[article de jailhouse] jailhouse hypervisor
Solution to the palindrome string (Luogu p5041 haoi2009)
用STM32点个灯
Educational Codeforces Round 116 (Rated for Div. 2) E. Arena
Dichotomy, discretization, etc
随机推荐
2017 USP Try-outs C. Coprimes
Graduation project of game mall
How to adjust bugs in general projects ----- take you through the whole process by hand
数仓项目的集群脚本
2022年贵州省职业院校技能大赛中职组网络安全赛项规程
Talking about JVM (frequent interview)
kubeadm系列-00-overview
Corridor and bridge distribution (csp-s-2021-t1) popular problem solution
Palindrome (csp-s-2021-palin) solution
Developing desktop applications with electron
The sum of the unique elements of the daily question
Daily question 2013 Detect square
剑指 Offer 05. 替换空格
After setting up the database and website When you open the app for testing, it shows that the server is being maintained
One question per day 2047 Number of valid words in the sentence
PC寄存器
The connection and solution between the shortest Hamilton path and the traveling salesman problem
Over fitting and regularization
Sword finger offer 04 Search in two-dimensional array
Sword finger offer 05 Replace spaces