
3.4 闭包
众所周知,JavaScript的一大特色就是闭包的存在,能够让内部函数访问到外部函数的变量。那么,我们该怎样认识闭包,以及在ES6标准下闭包会是怎样的呢?
【示例3-13】闭包代码:

打印出来的3个值均为25。在这个for循环内部为数组push了5个函数,代码为:
function () { return i*i }
我们期望这里内部函数i能够获取到每次循环的i值,执行test函数,并赋给test1。这样test1就成为了一个数组,并且使其内部元素均为函数。需要注意,这里内部的函数都还未执行,它们还没有在任何地方被调用。
在打印这一步时能够清楚地看到,开始调用其内部的函数。注意调用的时间是在打印这一步,那么此时的i值为多少呢?显然,此时已经在循环外部,i值为5。所以这也是打印值均为25的原因。
在JavaScript中,不存在块级作用域(先不讨论ES6),只有函数作用域。作用域的好处是在内部函数可以访问外部函数的变量。
【示例3-14】我们来改造一下这段代码:

这样循环每次执行时,将i作为参数实时传到内部函数中,这样便实现了保存每次i的值。但是和上面的函数不一样的地方是,我们这里获得的test1实际是一个由数字组成的数组,而不是函数,这和我们的目标是不符的。
【示例3-15】我们需要的是数组内每个函数都能保存其创建时的i值,那么简单,在之前立即执行函数的基础上,让它的返回值也成为一个函数就行,而参数既然能往下传一层,当然也可以传两层,修改上述代码:

结果为:0、1、4。
可以看到,我们只是在之前的基础上进行了一点加工,让外层匿名函数的返回值也变为一个函数,让内层函数的返回值成为我们需要的结果,由于内层函数保持着对传进来的参数i的引用,导致i的值一直保存在内存中没有被释放,使得我们在调用这个函数的时候仍然能获得每次i的值,这也就是我们所说的闭包的实现。
在ES6中获得了两个新的声明方式:let和const。let允许声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,var声明的变量只能是全局或者整个函数块的。
【示例3-16】将上述代码使用let进行修改:

相比于初始代码,整个函数有两处被替换:const arr = []以及let i = 0,由于let声明的块级作用域,每次循环的i都会被直接固定下来而不会受其他地方的影响,轻松实现了闭包的效果。
所以在ES6环境下,我们不需要用立即执行函数的形式来实现闭包,使用let声明块级作用域即可。