JavaScript新手必看之var在for循环中的坑
JavaScript新手必看之var在for循环中的坑
var这个关键字在JS当中是相当常用的,但同时配合到for循环的话会出现不符合预期的运行结果。
一道面试题
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。这样才能达到我们想要的效果
转载自:https://juejin.cn/post/7042931057439539230