在Web开发中,在使用JavScript时,就算没用过,也听说过闭包这个名词。闭将外部作用域中的局部变量封闭起来的函数成为闭包,本质就是一个函数。
闭包的作用

  • 保护函数内变量的安全,不能被外部随意修改,只能通过制定函数接口操作
  • 在内存中维持变量,不会被销毁,所以弊端是滥用可能造成内存泄漏

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function fun() {
var num = 9;
add = function() {
num++;
}
function clo() {
return num;
}
return clo;
}

//add();//这里还不能执行
var f = fun();//获得闭包函数
add();//操作num的值
var value = f();//执行函数获取局部变量num的值 11

以上例子最终的 value 值就是局部变量 num 的值, clo 函数就是闭包。
我们无法直接访问 num 变量,但根据Javascript的链式作用域,对于 clo , fun 内部的所有内部变量都是可见的,所以只要把 clo 作为返回值就可以在外部访问 num 内部变量了。
add 函数在这里是一个全局变量,在Javascript中,在函数中变量定义不加上 var 或者其他类型,会被视为全局变量,但要在函数执行后得到声明才能使用,如下

1
2
3
4
5
6
function test() {
var i = 1;//局部
j = 2;//全局,执行test函数时声明,之后全局可用
}
test();
console.log(j)

所以在 fun 函数执行后声明完成,就可以在外部执行,由于闭包 clo 维持了 num 变量,所以在外部 add 函数修改的 num 跟在 fun 函数中 num 的是同一个。

事实上, num 变量在函数中有点类似 Java 中的私有变量,外部不能直接访问,而需要函数内部其他方法访问。以函数对象写另一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var fun = function() {
var age = 23;
var name = "shen";
this.getAge = function() {
return age;
}
this.getName = function() {
return name;
}
this.setAge = function() {
age++;
}
}
var ff = new foo();
//ff.age;//访问不到局部变量
ff.setAge();
console.log(ff.getAge())//24
console.log(ff.getName())//shen

在 Java 中,也有类似的闭包实现存在,匿名内部类就是一个例子,它可以访问外部类的成员变量(相当于第一个例子的clo函数可以访问fun函数的num变量)。但是,由于Java无法保证内部类使用的外部类的局部变量在内外部类同步都指向同一基本类型数据或对象,因为Java只是把外部类的变量做一个拷贝丢给了内部类另一个变量而已。所以,只能干脆限制在内部类访问的外部类变量必须定义为 final 类型,即初始化后就不能修改
接口

1
2
3
public interface Cat{
public void eat();
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test {

public void eatSomething() {
//final int num = 10;
int num = 10;//final默认可以不写,但当你尝试对num进行操作,比如num++之类,编译器会报错(环境Java8)
Cat a = new Cat() {
@Override
public void eat() {
System.out.println("eat " + num + " fish");//访问外部num变量
}
};
a.eat();
}

//Java8支持更简便的Lambda表达式
public void LambdaEatTest() {
int num = 10;
Cat cat = ()->System.out.println("eat " + num + " fish");
cat.eat();
}

public static void main(String[] args) {
new Test().eatSomething();
new Test().LambdaEatTest();
}

}

因此,也有些人认为Java的闭包不能算是真正意义上的闭包,有不可修改外部变量的限制。