先上維基百科的定義
在電腦科學中,閉包(英文:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變數的函數。這個被引用的自由變數將會和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
簡單理解這句話,有兩個要點:
1. 自由變數 2. (引用自由變數的)函數。
當我們定義一個變數時,如果不對它指定約束條件,它就是自由變數。 舉例:
x ∈ (0,99) f(x,y)
在函數f(x,y)中,x就是約束變量,y是自由變數。
具體到JavaScript中,看一個例子:
var x = 0; function foo (y) { var z = 2; return x + y + z;}foo (3); // 3
轉換成數學思維的話,函數foo其實應該是這樣的foo(x,y) ,但是我們知道函數的參數其實是受到函數的限制的,也就是說,真正的自由變數只有x一個。
這樣可以引出一個簡單的定義,在函數中,如果存在一個既不是局部變量,也不是形參的變量,我們可以認為形成了閉包。
幾乎所有的語言中,對於同名變數都是就近尋找,先在本作用域內尋找,找不到就去父作用域找。我們稱之為作用域鏈。
在一個閉包函數中,自由變數通常是由父級提供。看下面的例子:
function foo(x) { var tmp = 3; function bar(y) { console.log(x + y + (++tmp)); } bar(10);}foo(2)
根據我們上面的定義,bar擁有自由變量,是閉包,而foo不是。
那麼怎樣才能讓foo變成閉包呢?
var x = 0;function foo() { var tmp = 3; function bar(y) { console.log(x + y + (++tmp)); } bar(10);}// 其实转换一下,形如function foo2() { var tmp = 3; //function bar(y) { console.log(x + 10 + (++tmp)); //} // bar(10);}
此時,可以認為foo是個閉包。
到這裡,可能有朋友覺得這和平時看到的js閉包不一樣啊,我們平時看到的閉包,都是這樣的:例子來自這篇博客
##
function foo(x) { var tmp = new Number(3); return function (y) { alert(x + y + (++tmp)); }}var bar = foo(2); // bar 现在是一个闭包bar(10);這個函數其實可以改寫成下面的樣子:
bar = function (y) { // foo(2) alert(2 + y + (++tmp))}很明顯,tmp是自由變量,符合我們起初的定義,bar是擁有自由變數的函數。
那麼tmp存在哪呢?
在執行foo(2)時,就會產生一個tmp=3的變數。這個變數被return的函數所引用,所以不會被回收。而return的函數中的自由變量,就根據作用域鏈去找值。 bar函數,是在foo(2)中定義的,所以,變數tmp先在foo(2)的變數區中去尋找,並對其操作。
註:有關作用域鏈的問題,我會在下一篇做解析。
說到這裡,插一下module模式。
閉包使用之module模式var Module = (function () { var aaa = 0; var foo = function () { console.log(aaa); } return { Foo: foo }})();// 或者(function () { var aaa = 0; var foo = function () { console.log(aaa); } window.Module = { Foo: foo }})();注意上面的兩個例子,Module本身只是一個對象,但是return的函數本身形成了閉包,保證了作用域的乾淨,不會污染到其他函數。 說到這裡,想必有朋友覺得這不就是個另類的類別嗎?擁有局部變量,還有可訪問的函數。沒錯,就外現而言,我認為閉包和類別是非常相似的。 類別以Java舉例:
class Foo { private int a; int Say( int b ) { return a + b; } }上面的Foo中,函數Say中的a是函數作用域外的,屬於自由變數。可以認為Say形成了函數閉包。但是與js不同的地方就在於,實例方法需要透過類別的實例也就是物件來呼叫。
在java的設計裡,明確了存取權限,private,protect,default,package,這是規範呼叫的創舉。這也使得java程式設計師很少會考慮閉包這種實現,因為變數和函數都有關鍵字來定義存取權限,歸屬於一個個類別中,明確且清晰。
每次呼叫閉包,就會產生一個作用域來存放一些閉包函數所需的自由變數。這很容易造成記憶體浪費。即使在Java程式設計中,也不建議隨便就新建物件。
在前一篇bind、call、apply中,我提到了一個觀點,因為是物件導向,所以存在綁定this的需要。
關於物件導向,我認為,物件導向的好處就在於,易於理解,方便維護和重複使用。這在多人開發大型專案時,是遠遠超過對效能的要求的。
即使在摩爾定律放緩的現在,相對於以前,內存也是非常便宜的,所以從遠古時代對於性能要求到極致,到現在普遍提倡代碼可讀性。
有超級大牛創造了這個繽紛的程式碼世界,為了讓更多人體會到程式設計的樂趣,他們設計了更容易理解的程式語言,發明了各種編譯器、解析器…
如果只是寫一個1+1的程序,是不需要物件導向的,如果人類能和機器擁有超強的邏輯和記憶,也是不需要物件導向的。
以上是js中閉包和類別如何理解的詳細內容。更多資訊請關注PHP中文網其他相關文章!