## JavaScript之闭包理解
闭包(Closure)是JavaScript学习过程中一个非常重要的问题
闭包和执行上下文和作用域链有着千丝万缕的关系。闭包是指有权访问另一个函数作用域的变量的函数(JavaScript高级程序设计(第三版)P178)
闭包是一个函数,对闭包的一个理解可以是所在的执行上下文已经出栈,但是仍然访问了其所在的执行上下文变量对象的函数
这里所指的所在的执行上下文指的是闭包函数对应的执行上下文,而不是闭包本省所对应的执行上下文
来一个简单的例子
1 | function A(){ |
那么他的执行上下文栈行为是这样的
1 | /*伪代码*/ |
我们都知道,JavaScript 拥有自动的垃圾回收机制,当一个值失去引用的时候,垃圾回收机制会根据特殊的算法找到它并将其回收。
函数的执行上下文在出栈后,其变量对象会失去引用等待被回收,而闭包的存在会阻止这一过程,因为闭包的作用域链包含了其所在执行上下文的变量对象。
通过上面的代码可以看出,在B函数执行的时候,A的上下文已经出栈了,按照JavaScript的垃圾回收机制,A上下文的变量对象失去引用后会被垃圾回收机制回收,但是由于B上下文作用域链包含了A上下文的变量对象,所以A上下文的变量对象不会被垃圾回收机制回收。
我们知道函数作用域是在函数被定义(声明)的时候确定的。每一个函数都会包含一个[[scope]]内部属性,在函数被定义的时候,该函数的[[scope]]属性会保存其上层上下文的变量对象,形成包含上层上下文变量对象的层级链。
那么刚刚的代码上下文应该是这样的,在创建的时候
1 | B.[[scope]]=[AContext.VO,globalContext.VO] |
在B被调用的时候,其执行上下文会被创建并入栈,此时会生成变量对象并将该变量对象添加进作用域链的顶端,并将[[scope]]添加进作用域链
1 | BContext.Scope = [BContext.VO].concat(B.[[scope]]) |
可见,B上下文的作用域链包含了A上下文的变量对象,并且由于B访问A中的变量,阻止了A上下文的变量对象被垃圾回收机制回收。
看一个面试题经常会遇到的一个关于闭包的很经典的题目
1 | var arr = []; |
在 arr[0] 函数执行之前,我们可以知道,全局上下文的变量对象如下所示
1 | globalContext = { |
在 arr[0] 被调用执行时,其作用域链在函数上下文的创建阶段被创建,其作用域链如下
1 | arr[0]Context = { |
arr[0]函数会在自身变量对象中寻找i(arr总没有i),所以会向上找,找到全局上下文变量对象中的i,所以输出3
那么如何解决闭包
立即执行函数
1 | var arr = []; |
ES6中的let
1 | var arr = []; |
setTimeout
也还可以利用setTimeout的第三个参数
1 | var arr = []; |