在網上,關於閉包的文章衆多。javascript
MDN文檔中說:java
閉包是函數和聲明該函數的詞法環境的組合git
不少文章中說:github
閉包是指有權訪問另外一個函數做用域中的變量的函數閉包
還有一篇文章,總結了閉包的四種定義。函數
最後,我決定去請教個人一個經驗豐富的同事。spa
他說:code
閉包就是閉着的包子對象
......blog
我發現閉包的最大難點,就是沒有一個明確的定義。
因而,我去其精華、取其糟粕,寫下這篇關於閉包但徹底不去定義閉包的文章。
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版的《辛德勒名單》。
那麼在這個過程當中,你認爲哪部分屬於閉包呢?