最近在看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