在網上,關於閉包的文章眾多。
MDN文件中說:
閉包是函式和宣告該函式的詞法環境的組合
很多文章中說:
閉包是指有權訪問另一個函式作用域中的變數的函式
還有一篇文章,總結了閉包的四種定義。
最後,我決定去請教我的一個經驗豐富的同事。
他說:
閉包就是閉著的包子
......
我發現閉包的最大難點,就是沒有一個明確的定義。
於是,我去其精華、取其糟粕,寫下這篇關於閉包但完全不去定義閉包的文章。
function outter(){
var name = '小強'
function inner(){
console.log(name)
}
return inner
}
var foo = outter()
foo() // '小強'
複製程式碼
上面這段程式碼,就是一個閉包。
(無論閉包的定義是什麼,這段程式碼基本上是通行的)
首先,如果套用這個定義:
閉包是指有權訪問另一個函式作用域中的變數的函式
那麼,函式inner
就是閉包,因為我們知道:
定義在函式內部的函式,是可以訪問外部函式的作用域的。
簡寫一下:
function outter(){
var name = '小強'
function inner(){
console.log(name)
}
inner()
}
複製程式碼
這種結構下,inner
函式還是有權訪問outter
函式作用域中的變數的,所以這是不是閉包?
(我也不知道)
上面程式碼,是一種最常見的函式巢狀。
當outter
函式執行時,會建立一個屬於outter
的執行環境及變數物件。
當inner
函式執行時,又會建立一個inner
的執行環境及變數物件。
它們的相同點是:
執行完畢之後,各自的執行環境及變數物件都會被銷燬。
儘管函式是一等公民,但是它們執行完畢後、變得“沒用”,JS很快將它們“滅門”,這就是JS垃圾回收機制。
它們的聯絡是:
inner
函式可以訪問到outter
函式的變數物件。
變數物件,顧名思義,就是儲存該函式自身變數的一個物件。
內部函式儲存所有外層函式的變數物件,形成了自己的作用域。
即inner
函式的作用域,包括自身的變數物件、outter
的變數物件和window的變數物件。
為什麼要儲存別人的變數物件?
因為對自己有用,自身沒有的話就可以去用外層的。
可以說,外層函式的變數服務於內部函式。
再回到這種形式:
function outter(){
var name = '小強'
function inner(){
console.log(name)
}
return inner
}
var foo = outter()
foo() // '小強'
複製程式碼
不同於普通巢狀,
這裡當outter
函式執行到最後時,將inner
函式return
了出去。
顯然,outter
已經執行完畢了,但是它的執行環境及變數物件都被銷燬了嗎?
並不是。
有一個倖存者,就是**outter
函式的變數物件**。
雖然outter
函式在return
之後,自身已經執行完畢。
但是,因為它return
的是巢狀在自己內部的函式inner
,並賦值給全域性變數foo
,這就導致:
-
一方面,
outter
函式執行完畢,outter
的變數物件理應被銷燬 -
另一方面,
inner
函式被賦值給全域性變數,隨時有可能被呼叫,那它的作用域不應該被破壞,其中的outter
變數物件也就不該被銷燬
上面已經說過:
內部函式儲存所有外層函式的變數物件,形成了自己的作用域
所以,就是因為還有用,所以outter
函式的變數物件並沒有在outter
執行後被銷燬,成為倖存者。
最終,當我執行foo()
的時候,
儘管outter
函式早已執行完畢,但依然可以列印出其變數name
的值'帥哥小強'。
而我想到的,是《辛德勒名單》這部電影。
1939年,波蘭在納粹德國的統治下,黨衛軍對猶太人進行了隔離統治。
這時,德國商人奧斯卡·辛德勒和德軍建立了良好的關係,他的工廠僱用猶太人工作,大發戰爭財。
猶太人遭到了德軍的大屠殺,辛德勒目睹了這一切之後十分震撼。
辛德勒讓自己的工廠成為集中營的附屬勞役營,在那些瘋狂屠殺的日子裡,他的工廠也成為了猶太人的避難所。
德國戰敗前夕,屠殺猶太人的行動越發瘋狂,辛德勒向德軍軍官開出了1200人的名單,傾家蕩產買下了這些猶太人的生命。
這個電影很有名,如果沒看過建議看一下。
同樣,在我們的JS世界中:
當一個函式執行完畢,它的執行環境及變數物件也會遭到一場屠殺,即垃圾回收機制。
在這場屠殺中,辛德勒用一份自己工廠員工的名單,使自己的工廠成為集中營的附屬勞役營,更成為猶太人的避難所。
而inner
函式,也有一份自己員工的名單,那就是作用域。
這份名單上,就包含了outter
函式的變數物件。
inner
函式被賦值給全域性變數,就好比辛德勒和德軍建立了良好關係,
它的作用域就成為變數物件的避難所,
因為outter
函式的變數物件被寫在inner
函式的員工名單(即作用域)中,所以才免遭殺害。
這就是JS版的《辛德勒名單》。
那麼在這個過程中,究竟哪部分屬於閉包呢?