当前位置:网站首页>对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。
边栏推荐
猜你喜欢

剑指 Offer 35.复杂链表的复制

Personal developed penetration testing tool Satania v1.2 update

游戏商城毕业设计

Some common problems in the assessment of network engineers: WLAN, BGP, switch

1.15 - 输入输出系统

Pointnet++ learning

7. Processing the input of multidimensional features

6. Logistic model

Dichotomy, discretization, etc

Codeforces round 712 (Div. 2) d. 3-coloring (construction)
随机推荐
shared_ Repeated release heap object of PTR hidden danger
How to adjust bugs in general projects ----- take you through the whole process by hand
wordpress切换页面,域名变回了IP地址
Wazuh開源主機安全解决方案的簡介與使用體驗
Pointnet++ learning
1.13 - RISC/CISC
[jailhouse article] performance measurements for hypervisors on embedded ARM processors
leetcode-3:无重复字符的最长子串
Sword finger offer 04 Search in two-dimensional array
网络工程师考核的一些常见的问题:WLAN、BGP、交换机
884. Uncommon words in two sentences
PC register
CF1637E Best Pair
Full Permutation Code (recursive writing)
Sword finger offer 05 Replace spaces
CCPC Weihai 2021m eight hundred and ten thousand nine hundred and seventy-five
Daily question 1984 Minimum difference in student scores
Analysis of backdoor vulnerability in remote code execution penetration test / / phpstudy of national game title of national secondary vocational network security B module
Time complexity and space complexity
1.15 - 输入输出系统