JavaScript 闭包

什么是闭包?

通俗的说法是对象是变量带着函数,而闭包是函数带着变量。但是在理解闭包之前估计也是很难理解这通俗的说法。还有一种观点说是 Javascript种所有发函数都是闭包。 不去探讨这个观点的正确与否,从这两者的说法中我们可以看到闭包一定是与函数有关,在Javascript中函数就是一块作用域,也就是说闭包是与作用域有关的。

在Javascript中,函数是一种特殊的对象,他能在其他函数里面声明,他可以被赋值给变量,他特殊的地方是可以被调用。在C/C++中,函数必须有声明,然后可以被调用,函数调用时会切换到一个新的上下文环境中执行代码。在Javascript中不是这样的,一个函数可以不被声明立即执行,函数执行的时候也不会切换上下文环境。

那么在Javascript中作用域是怎么样的呢?一个函数就类似于其他语言中的一对{},{}里面可以访问{}外面的变量,在函数里可以访问函数外的变量。在其他语言中函数无法访问外部函数的变量,只可以访问局部变量以及全局变量。Javascript中特殊的地方是函数可以访问外部函数的变量。

那么闭包是什么呢?无论用什么方法让一个内部函数(这个函数在别的函数内部)在外面执行,他会获得最原始声明处和当前执行处的作用域。

所以有人说闭包是可以读取其他函数内部的变量,这里的其他函数就是这个函数原始声明处的外部函数。

看看闭包

1
2
3
4
5
var a = 2;
function f1() {
console.log( a ); // undefined
}
f1();

为什么所有函数都是闭包呢?因为函数一定是声明在某一个上下文环境当中的,上面这个f1就是声明在全局环境中。f1的执行环境也是这个全局环境,和其他语言一样,函数内部访问到a并不令人感到意外,虽然结果是一致的,但是原因可能和你想象的不大一样。

1
2
3
4
5
6
7
8
function f1(){
var a=2;
function f2(){
alert(a); // 2
}
f2();
}
f1();

虽然可能与其他语言中有一些不一样,但是这并不意外嘛!

1
2
3
4
5
6
7
8
function f1(){
var a=2;
return function(){
alert(a); // 2
}
}
f2 = f1();
f2();

这个看起来有点意外,f1执行完之后返回了一个函数之后没有被释放吗?为什么f2执行的时候不会报错?这个就是闭包。无论它在哪里执行,它具有声明和执行时候的作用域。f2虽然在全局环境中执行,但是声明是在f1内部,所以f2之后执行的时候依然能够访问到f1内部变量a

1
2
3
4
5
6
7
8
9
function f2(){
console.log(a);
}
function f1(){
var a = 3;
f2();
}
var a = 2;
f1();

这个结果是多少呢?f2在全局环境中声明, 在f1内部执行。也就说说f2执行的时候f2具有全局作用域和f1作用域。那么哪个作用域优先呢?试一试!

1
2
3
4
5
6
7
8
9
function f2(){
console.log(a);
}
function f1(){
a = 3;
f2();
}
var a = 2;
f1();

那么这一段代码运行的结果是多少呢?为什么呢?理解了吗?

闭包的作用

首先是变量的封装隐藏。你看上面的例子,一个外部函数的返回值是一个函数。这个函数可以访问外部函数的变量,而没有其他办法可以访问这个外部函数的变量,实现了人为的封装,private性。

第二个作用是实现类似static count的效果,在内存中一直维持着这个变量。

缺点就是闭包锁住了这个环境,查找作用域的时候会带来额外的开销,并且内存的开销也会变大。