for循环中的作用域问题
先看一个最常见的for循环例子, 想必都知道会依次打印 0 1 2
for(var i = 0;i < 3; i++){
console.log(i)
}
接下来, 我们稍微改造一下, 使用一个数组去存放函数,函数打印索引 i
let list = []
for(var i = 0;i < 3; i++){
list[i] = ()=>{
console.log(i);
}
}
//测试i
console.log(i); // 3
//调用数组里的函数
list[0]() // 3
list[1]() // 3
list[2]() // 3
结果跟我们预想的并不一样,打印的是 3 3 3 , 不再是依次打印 0 1 2 了。 这和作用域有关,上述 for 循环使用 var 关键字声明的变量 i 为一个全局变量( 可在for 循环之外打印变量 i , i 并不是 undefined 而是 3 ), for 循环遍历结束之后 ,变量 i 的值变成了 3, 这个时候再去执行 list[0]( ) ,list[1]( ) , list[2]( ) ,里面存储的函数执行 console.log( i ) 的时候 ,访问的都是同一个全局变量 i = 3 , 所以上述打印了 3 3 3 。
解决方法1 : 既然已经明白是作用域引起的问题, 那把 var 定义的全局变量 i 改为改为由 let 定义的块级作用域内的局部变量 i 是不是就可以了呢?
let list = []
for(let i = 0;i < 3; i++){
list[i] = ()=>{
console.log(i);
}
}
//测试i
// console.log(i); 报错 ,i is not defined
//调用数组里的函数
list[0]() // 0
list[1]() // 1
list[2]() // 2
很显然, 这是可行的, 在 for 循环这个块级作用域以外不能直接访问变量 i ,打印 i 会报错;for 循环结束之后再去执行 list[0]( ) ,list[1]( ) , list[2]( ),访问到的变量 i 不再是同一个全局变量了,而是由 let 定义的块级作用域内的三个不同的局部变量 i
解决方法2 :闭包
let list = []
for(var i = 0;i < 3; i++){
list[i] = (function(j){
return function(){
console.log(j);
}
})(i)
}
//测试i
console.log(i); // 3
//调用数组里的函数
list[0]() // 0
list[1]() // 1
list[2]() // 2
可能这个时候有人要迷糊了,明明是用 var 关键字声明的全局变量 i ,在 for 循环以外也可以正常打印 i 为 3,为什么这个时候执行 list[0]( ) ,list[1]( ) , list[2]( ) 却正常打印了 0 1 2 ? 显然, 内部被返回的函数访问了外部函数的变量 j 形成了闭包 ,而这个被引用的变量 j 被存储到了内存当中。for 循环结束之后再去执行 list[0]( ) ,list[1]( ) , list[2]( ),访问到的变量 i 并不是同一个全局变量, 而是内存中三个不同的变量 i
这正是闭包的特点之一 : 闭包访问到的外部函数的变量存储在内存中, 可以防止变量被污染( 即避免取到意外的值 )