闭包是JavaScript中非常重要的特性,看过很多讲闭包的,最近在Stoyan Stefanov著的一本 Object-Oriented JavaScript中看到的讲解觉得十分易于理解,在此分享下:
现在,让我们先通过图示的方式来介绍一下闭包。
首先是全局作用域,我们可以将其看做包含一切的宇宙。
其中,可以包含各种变量(如a)和函数(如F)。
每个函数也都会拥有一块属于自己的私用空间,用以存储一些别的变量(和函数),所以,我们最终可以把示意图画成这样:
在上图中,如果我们在a点,那么就位于全局变量中,而如果是在b点,我们就在函数F的空间里,在这里我们既可以访问全局空间,也可以访问F空间,如果我们在c点,那就位于函数N中,这里我们可以访问的空间包括全局空间、F空间和N空间。其中,a和b之间是不连通的,因为b在F以外是不可见的,但如果愿意的话,我们是可以将c点和b点连通起来的,或者说将N与b连通起来,当我们将N的空间扩展到F以外,并止步于全局空间以内时,就产生了一件有趣的东西——闭包。
知道接下来发生神马不?N将会和a一样置身于全局空间,而且由于函数还记得在被定义时所设定的环境,因此它依然可以访问F空间并使用b。这很有趣,因为现在N和a处于同一个空间,但N可以访问b,而a不能。
那么,N究竟是如何突破作用域链的呢?我们只需要将它们升级为全局变量(不使用var语句)或通过F传递(或返回)给全局空间即可。下面,我们来看看具体是怎么做的。
闭包①
下面,我们先来看一个函数:
function f(){
var b = “b”;
return function(){
return b;
}
}
这个函数含有一个局部变量b,它在全局空间里是不可见的。
b //这是指输入命令b
b is not defined //这是执行结果
接下来,我们来看一下f()的返回值,这是另一个函数,我们可以把它看作上图中的函数N,该函数有自己的私用空间,同时也可以访问f()空间和全局空间,所以b对它来说是可见的,因为f()是可以在全局空间中被调用的(它是一个全局函数),所以我们将它的返回值赋值给另一个全局变量,从而生成一个可以访问f()私有空间的新全局函数。
var n = f();
n();
“b”
闭包②
下面这个例子最终结果与之前相同,但在实现方法上有一些细微的不同,在这里f()不再返回函数了,而是直接在函数体内创建一个新的全局函数。
首先,我们需要声明一个全局函数的占位符,尽管这种占位符不是必须的,但最好还是声明一下,然后,我们就可以将函数f()定义如下:
var n;
function f(){
var b = “b”;
n = function (){
return b;
}
}
现在,让我们来看看f()被调用时会发生神马:
f();
我们在f()中定义了一个新的函数,并且没有在这里使用var语句,因此它应该是属于全局的,由于n()是在f()内部定义的,它可以访问f()的作用域,所以即使该函数后来升级成了全局函数,但它仍然可以保留对f()作用域的访问权。
n();
“b”
闭包③
根据目前的讨论,我们可以说,如果一个函数需要在其父级函数返回之后留住对父级作用域链的话,就必须要为此建立一个闭包。
而由于函数通常都会将自身的参数视为局部变量。因此我们创建返回函数时,也可以令其返回父级函数的参数。例如:
function f(arg) {
var n = function () {
return arg;
}
arg++;
return n;
}
然后我们可以这样调用函数:
var m = f(123);
m();
124
请注意,当我们的函数函数被调用时,arg++已经执行过一次递增操作了,所以m()返回的是更新后的值,由此我们可以看出,函数所绑定的作用域本身,而不是该作用域中的变量或变量当前所返回的值。