我的JavaScript之旅——“闭包”是什么时候创建的
时间:2010-09-14 来源:菜阿彬
function Outer(){
var x = 1;
function Inner(y) {return x + y};
return Inner;
}
对于这样一个简单的闭包函数,下面两种调用方式有什么不一样的地方?
//方式1
var inner1 = Outer();
var result = inner1(2); //3
//方式2
var result = Outer()(2); //3
此篇试图解答这个问题。先复习一下:
上篇文章说到,每次执行一个function时,就会进入一个新的“执行上下文”(execution context)。context的几个重要属性:
1, 有一个对应的variable object;在global context中就是global object。
2, 有一个对应的scope chain,这个scope chain的第一个object就是variable object;
3, 有一个不变的this变量。
其中第2条,scope chain是JS实现闭包(closure)的关键所在,这篇对“闭包”展开描述,以加深印象。后续文章将对this专门探讨。
Variable object的实例化三部曲
我们已经知道,当JS执行时碰到一个变量,它会到scope chain里递归去找,而scope chain是由variable object和global object组成的一个object chain,global object包含JS预定义好的所有object,function的variable object则会包含函数内声明的所有东西,包括function的参数、内部函数、局部变量。创建Variable object的过程有三步,上篇有写过,这边是官方的文档。简述如下:
1, 为variable object创建与函数参数同名的属性;属性值为传入的参数值。
2, 对于“function declaration(见下节)”,首先创建函数,然后为variable object创建属性;属性值即为该函数实例。覆盖1的同名属性。
3, 为variable object创建各变量的属性;属性值为undefined。不覆盖1,2的同名属性。
function declaration vs. function statement
下面就是一个function declaration:
function A(){
}
下面是一个function statement:
var A = function A(){
}
这是创建函数的三种方式之二,区别在上面的三部曲中就可以看出来:
1, 在进入execution context时(还没执行任何代码),function declaration就已经在第2步创建函数实例起来了;而function statement属于第3步,而且初始值是undefined,要到执行这行代码时,函数才会被创建起来。
2, function statement不会覆盖同名的function declaration。
验证一下:
如图:A可以先用再声明;B则不行。B在执行到最后一句之前,都是undefined。但是B这个属性是一开始就在的:
(this是什么?因为上面的代码是执行在global context中,B属性应该创建到global object身上,也就是this。下篇会详述this。)
三部曲中说了,function declaration会覆盖同名的1里的参数,所以它是varaible object里的第一等公民:
而function statement不会。
闭包的创建时机
对于开头的这段代码:
function Outer(){
var x = 1;
function Inner(y) {return x + y};
return Inner;
}
然后执行
var inner1 = Outer();
回忆一下这时会发生什么?
会进入一个新的“执行上下文”(Outer Context),创建OuterVariableObject(有x和Inner属性),放到OuterScopeChain的最前方。而且会创建Inner函数,创建时把当前Scope Chain作为Inner函数的[[Scope]]属性。这是重点。
创建起来的Inner函数被inner1变量引用,Inner有[[Scope]]属性,引用了OuterScopeChain,即[OuterVariableObject, global object],而OuterVariableObject又引用了局部变量x。所以inner1变量就对Outer函数体内的局部变量x有间接的引用。内部函数对外部函数的变量有了引用关系——闭包就是这时产生的。每次对外部函数的调用,都会产生一次闭包。
garbage collection
很多人都听过闭包容易引起内存泄露。为什么呢?因为如上所述,inner1变量对x有间接引用,而inner1是声明在global context下的一个变量,它在global context下随时可以被用,那么JS的垃圾回收器就不会回收它(inner1),当然也就不会回收它所引用的x——直到退出global context,也就是我们关掉网页的时候。
这就是文章开头两种调用方式的区别:方式1,x在关掉网页前一直不能被回收;而方式2,x会被回收。
后续文章将介绍闭包的用处,和this关键字(比我想象的复杂)。