闭包是Javascript的一个基本概念,很多面试都会问到这一问题。如果到网上搜索可以找到很多对这个概念说明的文章。我看过很多这方面的文章,发现只有Mozilla的MDN上的这个文章讲得最为准确和简单。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
首先,我们来看看闭包的定义:
A closure is the combination of a function and the lexical environment within which that function was declared.
一个闭包是Javascript的函数和声明这个函数时它所包含的变量环境的组合。
这句话说得非常清楚,闭包是两个东西的结合体:其一是函数,其二是函数的词法环境(简单可以理解为变量环境)。
要了解闭包,首先我们要了解的是词法范围Lexical scoping(或者叫变量作用范围)。Javascript的语法和Java非常类似,但是在语法上最不相同的一点是变量的作用范围。在Java,C,C++中,函数体里只能访问全局变量或者函数内部定义的变量。在Javascript中,一个函数体里不光能访问全局变量,而且能访问外层函数里定义的变量,也就是说:一个变量在声明的函数体内可见,这个函数里的嵌入函数也能访问该变量。
请看下面的例子:
function init() {
var name = "Mozilla"; // name is a local variable created by init
function displayName() { // displayName() is the inner function, a closure
alert (name); // displayName() uses variable declared in the parent function
}
displayName();
}
init();
嵌入函数displayName中能直接访问外层函数里定义的变量name。这一特性,直接导致了Javascript中必须有闭包的概念。也就是说Javascript的函数其实并不是自我封闭的,一个函数的运行结果与它外部函数中定义的变量也有关系。Java和C++等面向对象的语言其实也有类似的语法,一个对象的成员函数体里是能够访问对象的成员变量的,成员变量其实都是定义在成员函数外部,我们说到一个对象的成员函数,它其实包含了成员函数所在的对象环境。在Javascript中,为了表达这种函数与其外部环境的关系,所以引入了闭包的概念。
为了更加深入的理解闭包的概念,我们再来看下面的例子:
function makeFunc() {
var name = 'Mozilla';
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
学过Java或者C++的人都知道,在一个方法内部定义的局部变量,在这个方法执行完后,局部变量的空间就会被释放,这个局部变量也就不再存在。但是在上面的例子中,makeFunc()这个函数执行完后,显然这个函数中定义的局部变量name还在继续存在,不然当myFunc()执行时会出错。
从这个例子我们可以看出,在Javascript里,当我们引用一个函数时,实际上被引用的函数外层的语法环境(也就是变量环境)实际上还是同时存在的,他们在一起构成了一个闭包。
我们可以利用闭包这一特性来实现一些有趣的功能。例如,在下面的例子中它通过闭包把函数和函数要处理的数据绑定在一起,生成了新的函数:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
另外我们要理解的是闭包的作用域链(Closure Scope Chain),这实际上也是理解Javascript里的变量作用域。我们知道闭包有三个作用域:
Local Scope (Own scope) 函数本身的作用域
Outer Functions Scope 外层函数作用域
Global Scope 全局作用域
其中要注意理解的是外层函数作用域。实际上,一个函数能访问它的所有外层函数中定义的变量,而不仅仅是它的直接外层函数。通过以下的例子我们就能了解,在最内层的函数实际上能访问所有外层函数定义的变量。
// global scope
var e = 10;
function sum(a){
return function(b){
return function(c){
// outer functions scope
return function(d){
// local scope
return a + b + c + d + e;
}
}
}
}
console.log(sum(1)(2)(3)(4)); // log 20
关于闭包的性能考虑。从上面的说明我们知道,我们对一个函数的引用包含了函数的语法环境,即包含了该函数所引用的所有外层函数中的变量定义。所以我们在引用一个函数时,其可能占用的内存会很大,我们最好能避免。
网友评论