我们先看下面一个例子:
function fun(){ var arr = []; for(var i = 0; i < 10; i++){ arr[i] = function (){ return i; } } return arr; } var fn = fun(); console.log(fn[0]()) //10 console.log(fn[1]()) //10
可能以为会打印0,1。但结果却都是10。
对这个问题我们可以用let定义i来解决
将 var i 改为 let i 打印出来的就是 0,1
但 let 有一些限制条件
比如 如果我们需要在for循环的外面 打印 i -> console.log(i),使用let的话就会报错,提示 i is not defined.
因为 let 只在{}里面的部分有效。并且 let定义变量时需要在调用该变量的前面,并不会将变量声明提前。
也可以通过闭包来解决
首先我们先了解什么是闭包(通俗易懂的)
我们知道在js中 可以在子级访问父级中定义的变量,但不能在父级中访问子集的变量。
这是作用域与作用域链造成的。
下面我们来粗略了解一下作用域链
作用域链
作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量
当需要查找某个值时,从自身开始找,找不到就往父级找,依次往上找。都没有则报错
作用域链上有两个对象,第一个时定义函数参数和局部变量的对象,第二个是全局对象。如果在嵌套的函数中,作用域上的对象数更多。
基本方式是入栈排列。自身占第一个,后面是父级,再是父级的父级,依次,知道全局GO。
值得注意的是,对于嵌套函数来说,每次调用外部函数时,内部函数又会重新定义一遍。所以每次调用外部函数时,内部函数的代码都是相同的,他们的作用域链不同。如下例子:
function fun(){ var num = 0; function jia(){ num++; console.log(num); } function jian(){ num--; console.log(num) } return [jia,jian]; } var jia = fun()[0]; var jian = fun()[1]; jia() jian()
打印结果是 1,-1. 而不是1,0.
那我们想在父级中访问子集的变量,像这种内部函数的作用域链仍然保持着对父级函数活动对象的引用,就是闭包。
通过上面的这个例子来说明:
将代码改变一下:
function fun(){ var arr = []; for(var i = 0; i < 10; i++){ (function(i){ arr[i] = function (){ return i; } })(i) } return arr; } var fn = fun(); console.log(fn[0]()) //0 console.log(fn[1]()) //1
这样使用闭包后就可以打印出 0,1 了
当内部的函数被保存到了外面,就会形成闭包。
但闭包也有自己的缺点:占内存。
占内存:闭包导致原有的函数执行完成了以后作用域链不会得到释放,会占据大量的内存。