Fork me on GitHub

理解JavaScript闭包

最近把 JavaScript 的基本语法大致看了一下,JavaScript 中的闭包概念理解起来还是有一点难度的。在看了一些资料和结合 Java 的的一些经验,感觉对闭包的概念稍微有了一些概念,这里记录一下。

什么是闭包?

网上看了很多解释,觉得比较简明的一个解释放上:有权访问另一个函数作用域变量的函数都是闭包。

接下来剥丝抽茧,一步一步的分析 JavaScript 中的闭包。首先我们可能会碰到一个计数的需求,可能用来记录用户的某个操作次数:

1
2
3
4
var count = 0;
function add() {
return count += 1;
}

很简单,但是有一个问题,这样声明的的变量 count 是属于 window 的,页面的其他脚本也可以改变 count 值,并不需要去调用 add() 函数。如果我们希望外部无法访问到 count ,只可以通过调用 add() 来增加 count 的计数该怎么办呢?

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

var a = new add();
console.log(a());
console.log(a());
console.log(a());

// 输出:
1
2
3

可以看到正确工作了,这种在匿名函数中访问外部函数的局部变量的操作,就是闭包。其实这种操作非常的像 Java 中的内部类,这里用 Java 代码来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Count {
private int count = 0;
private InnerClass counter = new InnerClass();

public class InnerClass {
public int add() {
return count += 1;
}
}

public static void main(String... args){
Count c = new Count();
System.out.println(c.counter.add());
System.out.println(c.counter.add());
System.out.println(c.counter.add());
}
}
// 输出:
1
2
3

在 Java 中,普通的内部类持有一个外部类的引用,所以天然的可以访问外部类的成员(即使是私有成员)。这里的 Count c = new Cout(); 在对象 c 初始化也初始化了变量 count 和 InnerClass counter。而在 JavaScript 中,其实也可以这么理解:var a = add(); 实际上就是创建了一个函数对象,var count = 0;这里是在对象初始化的时候执行的,而内部定义的匿名函数则是后续被调用的。后续的 a() 调用的实际上调用的是函数对象中的匿名函数。