闭包的定义
○ 从概念上讲,闭包是“包含上层作用域变量的函数”与“函数相关引用环境”两者组成的实体。其中的“函数相关引用环境”就是函数的词法环境链,是所有函数被执行前都会被创建的。因此在广义上只要函数内部引用了外部变量就构成闭包。词法环境相关内容整理在这篇。
○ 经常讨论的闭包出现在“局部作用域中内嵌函数”的场景,且同时满足两个要点:
(1)内嵌函数引用了上层作用域的变量
(2)内嵌函数被传递到所在作用域的外面
○ 对于函数作用域构成的局部作用域来说,也就是函数内嵌函数的场景,当内嵌函数引用了外部函数作用域内的变量,内嵌函数又通过 return 或赋值方式传递到外面,就可以达到“外部函数执行结束后仍可访问外部函数作用域内变量”的效果,如此就构成了一个闭包。
○ 在块级作用域中的内嵌函数,使用同样思路也可以构成闭包,如下例:
1 | var things = {}; |
☆解析:根据MDN的说明,for括号中使用var声明的变量存在于for循环体外面,而使用let声明的变量存在于for循环体之内的块级作用域。
闭包的用途
◆ 从外部访问局部作用域内的内容。
◆ 将局部内容保持在内存中。
理解实例
实例 1
1 | function f1(){ |
☆说明:“函数内嵌函数”的经典场景。当函数f1执行完毕,其中被闭包引用的局部变量f1v不会被垃圾回收机制回收。
实例 2
1 | var oneArr=[1,2,3]; |
☆解析:传入setTimeout的函数中引用了外部变量i构成广义闭包,但var声明的i变量存在于循环体外面的全局作用域,闭包中保留的i也是全局作用域中的变量i,setTimeout的回调函数执行的时候,i值已经是for循环执行完毕后的值3,将打印三次oneArr[3]。
实例 3
1 | var oneArr=[1,2,3]; |
☆解析:在为setTimeout传入回调函数的位置,通过构造“函数内嵌函数的闭包”将局部作用域参数index保持在了内存中。
实例 4
1 | var oneArr=[1,2,3]; |
☆解析:“块级作用域内嵌函数的闭包”场景,内嵌函数引用局部作用域的变量i,又将内嵌函数以传入setTimeout注册回调函数的方式传出。