最近在看js,看到closure(闭包)这一块儿的时候就想到了 java的匿名内部类 两者都有涉及到变量/参数的引用问题。
先说java的匿名内部类,他的定义我就不多做说明了,可以参考地址
http://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html
。我们今天主要说:
1、Why cannot an anonymous class access local variables in its enclosing scope that are not declared as final
or effectively final.
为什么java匿名内部类的方法中用到的局部变量必须定义成final或者是无修改的(实际上也是final的)
2、js的闭包又是什么,和参数引用又有什么关系
在说明这两个问题之前,我们必须先知道几个概念就是变量的scope(作用域)和lifetime(生命周期)。scope是针对编译期,包含变量的block(块,花括号)内。而生命周期则是指程序在运行时,给变量在内存上从分配空间到释放的整个时期。
一般来说,局部变量(local variable)都是分配在stack(栈)上的,在方法运行完后随着stack的坍塌,局部变量所占用的内存也随之释放
那么我们先来说第二个问题:
var quo = function (status) {
return {
get_status: function ( ) {
return status;
}
};
};
// Make an instance of quo.
var myQuo = quo("amazed");
document.writeln(myQuo.get_status( ));
对js来说,函数内部可以直接读取全局变量,而在函数外部自然无法读取函数内的局部变量。出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,一般情况下,这是办不到的,只有通过变通方法才能实现。js里的这种机制就叫闭包(closure)。上述代码就是一个闭包,我的理解是,闭包就是能够读取其他函数内部变量的函数(英文文档的解释可能更准确)。通过闭包,上述代码中,局部变量status的lifetime延长,其内存并没有随着函数quo()的完成而被释放。其底层的实现机制可能类似于是,将closure捕捉的局部变量放在堆上而不是栈上。
然后说说第一个问题:
public Runnable f(int x) { int i = 0; Runnable r = new Runnable() { @Override public void run() { System.out.println(i); i = 10; } } i = 100; return r; }
在java中,一般来说,在方法执行完后,其方法内的局部变量也随之释放。而当java匿名内部类的方法中用到局部变量时,使用的是变量的copy,而不是变量本身,所以为了在方法执行完以后,这个局部变量的copy没有被释放掉,这个局部变量的copy就没有被分配到这个方法的栈上,而是分配到对上,从而延长了他的lifetime。(参看上述java代码例子)而之所以要求变量是final的其实是要求该局部变量是不可变的,因为当局部变量i改变时,而匿名内部类r已经返回,而r拿到的是i原来值(i=0)的copy,i后来的值得改变,r是不知道,那r的存在就没有太多的实际意义,这显然是不合适的。所以java为了克服这个问题,就规定java匿名内部类的方法中用到的局部变量必须是不能修改的(即要么定义为final类型的,要么是在方法内不是不修改的),这样方法内局部变量和java匿名内部类的方法中用到的局部变量就保持了一致。有没有种是js阉割版闭包的感觉~
注:之所以把这两个问题放在一起说,只是因为他们在解决问题上的思路是上有相同之处。并不是说java和js有很多的相同之处。即使在这闭包这个问题上,也能看出js和java这两门语言之间很大的一个不同点,js闭包中内部函数访问的是局部变量本身,而java匿名内部类的方法中用到的局部变量则是局部变量值的拷贝。希望本文没有给你造成这种js与java类似的误解。
更多关于JavaScript 的closure的可以看看:http://www.jb51.net/article/24101.htm