一、作用域链
闭包这个概念最初接触起来是有一定难度的,它的难点在我看来,是对 JavaScript 中作用域的理解,因此我们首先再来回顾一下作用域的相关知识点:
- 在 JavaScript 中,变量的定义并不是以代码块作为作用域的,而是以函数作为作用域的。
- 也就是说,如果一个变量是在某个函数中定义的,那么,它在函数以外的地方是不可见的。
- 而如果,该变量是定义在 if 或 for 这样的代码块中的,那么,它在代码块以外的地方是可见的。
- 所谓 全局变量,是指定义在所有函数之外的变量,定义在某个函数中的变量(显式的)则称之为局部变量(私有)。
- 定义在函数之外的变量在函数内是可见的,反之则不然。
我们来看个例子:
var a = 123;
function f(){
var b = 456;
return a;
}
//调用一下
f(); //123
b //ReferenceError: b is not defined在这里,变量 a 是属于全局域的,而变量 b 的作用域就在函数 f( ) 内了。因此:
- 在 f( ) 内,a 和 b 都是可见的
- 在 f( ) 外,a 是可见的,b 则不可见
在下面的例子中,如果我们在函数 outer( ) 中定义了另一个函数 inner( ),那么,在 inner( ) 中可以访问的变量既来自它自身的作用域,也可以来自其“父级”作用域。
var global = 1;
function outer(){
var outer_local = 2;
function inner(){
var inner_local = 3;
return inner_local + outer_local + global;
}
return inner();
};
outer(); //6像这样,这就形成了一条 作用域链 (scope chain),该链的长度(或深度)则取决于我们的需要。
二、利用闭包突破作用域链
单纯的去理解闭包的概念是什么,在我看来,没有太大的意义,因此,从它能干什么?它的作用是什么?这个角度,我觉得更能说明它本身。先来看一个例子:
var a = "global variable";
var F = function(){
var b = "local variable";
var N = function(){
var c = "inner variable";
return b;
};
return N;
};从上面的例子中,我们可以看出:
- 变量 b 在函数 F( ) 以外是不可见的:
b //ReferenceError: b is not defined- 函数 F( ) 和函数 F( ) 的 私有函数 N( ) ,都是一个返回函数的函数:函数 F( ) 的返回值是它的私有函数 N 的引用,而私有函数 N( )的返回值,是与其”同处一室“的变量 b ;
- 在作用域链的影响下私有变量 b 在私有函数 N( ) 中是可见的;
那么,接下来就是有趣的地方了,即当我们把这个 返回函数的函数 F( ) 赋值给另一个全局变量的时候,就会生成一个可以访问该函数 F( ) 私有空间的新全局函数:
var newGlobal = F();
newGlobal(); //"local variable"像这样,我们通过 作用域链+私有函数+返回函数的函数 的形式,就打破了作用域链原本固有的规则(函数内定义的变量在该函数外不可见),使之可见了。这就是闭包。
下面的例子的最终结果与之前相同,但在实现方法上存在一些细微的不同。在这里 F( ) 不再返回函数了,而是直接在函数体内创建一个新的全局函数 newGlobal( ):
var newGlobal;
var F = function(){
var b = "local variable";
var N = function(){
return b;
};
newGlobal = N;
};
F();
newGlobal(); //"local varoable"如上,我们需要声明一个全局函数的占位符。尽管这种占位符不是必须的(见下文),但最好还是声明一下,这样,我们我们仍然通过 作用域链+私有函数+变量(提升)的形式,创建了一个闭包。
另外,我们在《变量的作用域》一文中提到过:在函数中,如果我们声明一个变量时没有使用 var 语句,那么,该变量就会被默认提升为全局变量,因此,上面的例子,还可以写成这样:
var F = function(){
var b = "local variable";
var N = function(){
return b;
};
inner = N;
};
f();
inner(); //"local variable"综上所述,我们究竟是如何利用闭包来突破作用域链束缚的呢?我们只需要将他们升级为全局变量(不使用 var 语句)或通过 F 传递(或返回)给全局空间即可。
本文摘自《JavaScript面向对象编程指南》,分享的目的仅供个人学习和理解,如需转载请备注本文出处,谢谢!