闭包
1. 函数的词法作用域规则
基本规则:函数在执行过程中所用到的作用域是在函数定义时的作用域,而不是函数调用时的作用域。
如何理解这个规则呢?来看两个例子。
例子一:
这个例子的执行结果是
小龙
。由于函数fun()
不管是定义还是调用都是在同一个作用域中(如下图)。所以这无法知道函数执行过程中所依赖的作用域是哪一个作用域。
例子二:
在这个例子中,函数
fun()
定义时的作用域和调用时的作用域并不在同一个作用域中(如下图)。那么执行结果会是什么?答案是小龙
,而不是小鱼
。我们知道
小龙
是在函数fun()
定义时的作用域下,而小鱼
是在函数fun()
调用时的作用域下,这说明函数在执行过程中所依赖的作用域是定义时的作用域。
2. 作用域链
如果在当前作用域中没有查找到所需的值,就会向上层作用域查找,直到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
函数
getter()
执行时会先在当前作用域中查找所需的变量,如果当前作用域没有,就会往上一层作用域查找,以此类推,直到全局作用域。如果连全局作用域都没有该变量,则会报错:xxx is not defined
。上面例子中查找变量过程中形成的链条( 3 --> 2 --> 1)就是作用域链。
3. 解释闭包
- 《JavaScript高级程序设计》
- 闭包是指有权访问另一个函数作用域中的变量的函数。
- 《JavaScript权威指南》
- 从技术的角度讲,所有的函数都是闭包:它们都是对象,它们都关联到作用域链。
- 《你不知道的JavaScript》
- 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
个人理解闭包应该是基于词法作用域书写代码时产生的自然结果,是一种现象。
例子一:
这个例子中,函数
getter()
能够获取到变量userName
有两种解释:1. 作用域链 2. 闭包,所以这里不太好观察闭包这个现象。
例子二:
在这个例子中,函数
getter
是在函数userInfo
的词法作用域外面执行,但依然能拿到变量userName
,这不就是上面所说的函数的词法作用域规则,所以闭包就是基于函数词法作用域规则下产生的一种现象。简单理解就是一个函数中嵌套另一个函数,这两个函数之间就产生了闭包。
4. 闭包作用
让外部可以获取到函数内部的局部变量
让函数内部的变量可以一直保存在内存中
这个例子就是利用了闭包实现了全局变量
getUserName
对函数getter
的引用,从而达到让外部获取到函数内部的变量。
疑问一:当一个函数调用结束之后,函数的本身以及内部的局部变量都会被垃圾回收机制回收。那为什么函数
userInfo
调用结束之后还可以获取到其局部变量userName
。
解答:原因在于
userInfo
是getter
的父函数,而getter
被赋给了一个全局变量,这导致getter
始终在内存中,而getter
的存在依赖于userInfo
,因此userInfo
也始终存在内存中,不会在调用结束之后被垃圾回收机制回收。
5. 使用的注意点
- 由于闭包会使得函数中的变量一直保存中内存中,内存消耗很大,所以不能滥用闭包