闭包的官方的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
通俗点的说法是:
- 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
- 从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量(自由变量:是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量)
function testFreedom() { var freedomVar = 1; function inner(param) { echo(pclosure1, param + freedomVar);//将结果打印到pclosure1中 } return inner; }
对于inner函数来说,freedomVar就属于自由变量。
一、一个实现
<p id="closure1" style="color:red"></p>
function echo(p, html) { p.innerHTML += html + '<br/>'; } function closure() { var innerVar = 0; function inner() { return ++innerVar; } return inner; } var quote = closure(); echo(pclosure1, quote());//1 echo(pclosure1, quote());//2
1、第一次quote函数的结果为1,第二次为2
2、inner嵌套在函数closure内部;函数closure返回函数inner
3、结合上面的闭包实践角度特点,可以得出closure是一个闭包
4、函数closure在返回后不会被GC回收,原因如下:
- closure返回函数inner的引用给quote
- 函数inner的作用域链包含了对函数closure的活动对象(activation_1)的引用,如下图所示。
- inner可以访问到closure中定义的所有变量和函数
- 函数inner被quote引用
- 函数inner又依赖函数closure
二、大致的作用域图
三、closure里面的大致执行步骤
1) 初始化Global Object即window对象,Variable Object(全局执行环境中的可变对象)为window对象本身。创建Scope Chain对象,假设为scope_1,其中只包含window对象
2) 扫描JavaScript源代码,从结果中可以得到定义的变量名、函数对象。按照扫描顺序:
- 发现函数closure的定义,使用这个定义创建函数对象,传给创建过程的Scope Chain为scope_1。将结果添加到window的属性中,名字为closure,值为返回的函数对象
- 发现变量quote,在window对象上添加quote属性,值为undefined
3) 执行函数closure,得到返回值:
3.1 创建Activation Object,假设为activation_1;创建一个新的Scope Chain,假设为scope_2,scope_2中第一个对象为activation_1,第二个对象为window对象
3.2 处理参数列表。创建arguments对象并进行设置,将arguments设置为activation_1的属性
3.3 对closure的函数体执行类似步骤2的处理过程:
- 发现变量innerVar,在activation_1对象上添加innerVar属性,值为undefined
- 发现函数inner的定义,使用这个定义创建函数对象,传给创建过程的Scope Chain为scope_2(函数closure的Scope Chain)。将结果添加到activation_1的属性中,名字为inner,值为返回的函数对象。inner的内部 [[Scope]]就是scope_2
3.4 执行innerVar赋值语句,赋值为"0"
3.5 执行inner:
- 创建Activation Object,假设为activation_2;创建一个新的Scope Chain,假设为scope_3,scope_3中第一个对象为activation_2,接下来的对象依次为activation_1、window 对象(取自fn2的[[Scope]],即scope_2)
- 处理参数列表。因为inner没有参数,所以只用创建arguments对象并设置为activation_2的属性
- 对inner的函数体执行类似步骤2的处理过程,没有发现变量定义和函数声明
- 执行函数体。对任何一个变量引用,从scope_3上进行搜索,这个示例中,innerVar将在activation_1上找到
- 返回inner的返回值
3.6 返回结果
4) 打印结果