var f = function( ) {
var x = 1
function fo() {
console.log( x++ )
}
return fo
}
var fn = f()
fn() // 1
fn() // 2
以上代碼實現了一個簡單的閉包,每次執行 fn 其內部變量 x 都會實現自增。
閉包用一種巧妙的方式將原本互不敢干的作用域聯合了起來,我在之前的文章
JavaScript之引用函數調用與直接調用的區別 說到作用域只有在代碼塊執行的時候才會產生,還有一點沒有提到:作用域所占的內存是如何回收的,以及何時釋放。
閉包一直飽受詬病,下面具體為大家分析下我的觀點:
事實上,函數體包裹的每塊代碼在每次執行都會新產生一個作用域,然后在函數體執行完后被釋放,我們平時在執行這一個過程的時候常常忽略了這樣一個事實,所以在接觸到閉包這些概念的時候短時間會無法接受。當然,如果你琢磨透了的話,並沒有什么難理解的,比如函數體內定義了一個變量 x = 1,每次你調用這個函數,在其內部所引用的 x 值都是1,這意味着這個變量在上次函數體被執行完后就釋放了,一個獨立的作用域被執行完后就會被銷毀。
搞明白了上面這點,也就不難理解上面的代碼,如果將函數體從一個函數內發送出去,那么就意味這此函數的作用域發射到了其上級作用域內,即兩個作用域聯合了起來,融合到一起了,這時候JavaScript再想銷毀這個作用域就會連同 window 這個作用域一起銷毀,顯然它不會這么干,於是,此例中函數 fo 的作用域,也即 f 所代表的函數體(作用域)被留存了下來。
再引申開來,如果 f 這個函數體再執行一次會怎樣呢?這時候一個新的函數體又被執行了,一個新的作用域產生又匯入了父級作用域,如果你想銷毀這個作用域,又是不可能的。以上面這個例子來說,就有兩個值為 1 的變量 x 進入了全局作用域,全局作用域不得不分配兩個不同的隱式的變量指向這兩個 x ,用行話來說,就是產生了全局污染。
var f = function( ) {
var x = 1
function fo() {
console.log( x++ )
}
fo.alter=function ( n ) {
x = n
}
return fo
}
var fn = f()
fn() // 1
fn() // 2
fn.alter( 10 )
fn() // 10
為了給這個函數開一個后門,使得變量 x 可以更改,給函數對象 f 增加一個 alter 的方法。
關鍵點在於,此方法也是在 f 的函數體內部定義的,意味着調用也會在此作用域執行,因此更改變量 x 的值也就順利成章了。