JavaScript新手必看之var在for循环中的坑
作者:JetTsang
一道面试题
for(var i = 0;i<5;i++){ console.log(i) }
那么以上会输出什么呢?答案是控制台是依次输出0,1,2,3,4。相信大家小伙伴们都答对了。再接再厉吧,再来一道。
for(var i = 0;i<5;i++){ setTimeout(function(){ console.log(i) }) }
这次还会是同样的结论吗?答案是输出5次5。这里开始有疑惑了对吧,预期输出应该也是0,1,2,4才对,怎么会输出的是5呢?先开个结论,这里是和作用域有关系的。
引申
为了更进一步的去理解这个问题,来一个需求吧。用一个数组去存放函数,依次输出0-4之间的数吧。
var a = [] for (var i= 0;i<2;i++){ a[i] =function(){ console.log(i) } } a.forEach(_=>{ _() })
答案同样是只能输出5。
原因就很简单,因为你的每一个函数都绑定的变量i,所以每次去执行函数,都会去访问这个变量i,因为var声明的变量,并不只局限在for循环当中,而是在全局当中生效了!而你又不是在循环当中去调用它的,而是在循环之后去调用。在循环时,i会伴随着循环增长,此时你调用的话,前面的确实a[i]的结果是i,但a[i-1],a[i-2]...的结果也是i,因为函数调用时,内部的i指向的是全局范围内的i。
换言之,你数组里的函数都是引用的这个全局变量i。而不是for循环里的局部变量i。
要想解决这个问题,请接着往下看。
解决思路
思路:既然var声明的是全局的变量,那么只要函数里的变量是局部的即可。
写法1
巧的是ES6当中的let声明关键字就是这个效果。
var a = [] for (let i= 0;i<2;i++){ //这里把原来的var声明改成了let声明 a[i] =function(){ console.log(i) } } a.forEach(_=>{ _() })
那么可能会疑问,既然这个let声明的i是局部变量,那么每次循环都会重新创建1个i吧?
是的没错。
那么每次都重新创建的话,会不会i的值也会被重新初始化呢?
答案是不会,JS引擎在for循环当中会记住前一次结束时的i值,并且在下一次创建时将i赋值。
写法2
var a = [] for (var i= 0;i<2;i++){ a[i] = (function(i){ return function(){ console.log(i) })(i) } a.forEach(_=>{ _() })
这里的写法就是在每次循环当中,将循环中的i(i在不断增长),通过形参传进去,从而诞生出局部变量i。
附:形参传递的过程,基本数据类型就是将值赋给形参,而引用数据类型则是将指针赋给形参。
当心
可能会有这样想法的同学。这样做只是定义了函数,这个函数有1个形参i而已。这样你调用它就变成了a[i](xxx)。
var a = [] for (var i= 0;i<2;i++){ a[i] = function(i){ console.log(i) } } a.forEach(_=>{ _() })
想要传递参数只能是(fun(i){})(i),写成立即执行函数调用它,这样才能去给它传值(形参)。
var a = [] for (var i= 0;i<2;i++){ a[i] = (function(i){ console.log(i) })(i) } a.forEach(_=>{ _() })
而加入括号的时候,就会被执行了,因此我们需要套一层return。这样才能达到我们想要的效果
想要传递参数只能是(fun(i){})(i),写成立即执行函数调用它,这样才能去给它传值(形参)。
而加入括号的时候,就会被执行了,因此我们需要套一层return。这样才能达到我们想要的效果
到此这篇关于JavaScript新手必看之var在for循环中的坑的文章就介绍到这了,更多相关var for循环内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!