逗比的我,為你講“閉包”。

語言: CN / TW / HK

theme: simplicity-green highlight: a11y-dark


小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。

本想寫call、apply和bind的原理,寫著發現他們與this相關,我就去看this,然後發現this和作用域相關,最後就看到了閉包。這裡看的書籍《你不知道的JavaScript(上卷)》。

WeChat63aaa71a6e3fdd36d262bfcf5ce25c17.png

首先

我甩你一段程式碼: ``` js function father() { var fatherVal = 'father' son() function son() { var sonVal = 'sonVal' console.log(fatherVal,' ',sonVal); //father sonVal } }

father() `` 請問這個是閉包`嘛?

行行行,我知道你們說不是。下面這個呢?(自問自答的樂趣)。

``` js function father() { var fatherVal = 'father' function son() { var sonVal = 'sonVal' console.log(fatherVal,' ',sonVal); //father sonVal } return son }

var result = father()

result() 好吧好吧這個的確是閉包,那麼你們知道下面這個嘛? js var result function father() { var fatherVal = 'father' function son() { var sonVal = 'sonVal' console.log(fatherVal,' ',sonVal); //father sonVal } result = son }

father() result() 這個你也知道,那麼下面這個呢? js function father() { var fatherVal = 'father' function son() { var sonVal = 'sonVal' console.log(fatherVal,' ',sonVal); //father sonVal } result(son) }

function result(fn) { fn() }

father() 啊!這個你也知道,放殺手鐗了。 js function father(value) { setTimeout(function(){ console.log(value); }, 1000) }

father('fatherValue') ``` 累死了,什麼!!這個你也知道,對不起!浪費了您幾分鐘時間。

Background (3).png

你們都知道行了吧,我就皮,我寫給以後的自己看。既然這些都是閉包那麼它們有啥共同性呢?

Background (8).png

正文

這裡先來稍微講解下作用域把,然後再引出閉包。

js作用域

我反手一段程式碼丟上去: ``` js var wd = 'window' function father(msg) { var fatherValue = 'father' function son() { var sonValue = 'son' console.log(); console.log(wd,'--',fatherValue,'--',msg,'--',sonValue); //window -- father -- 我是father的引數 -- son } son() }

father('我是father的引數') ``` 這個程式碼大家都能看的懂,但是我還是畫了一張圖,大家就多浪費幾秒鐘看一眼。

0cdb43b2acfb78bc0a44bae7943a410.png 這就是常規的作用域, 有點冒泡的感覺。

閉包

我再反手一段程式碼: ``` JS var wd = 'window' function father() { son() function son() { var sonValue = 'son' } }

son() console.log(sonValue); ```

這就是一段錯誤的程式碼,從之前的作用域中我們是son中可以windowfather作用域上面的。這裡我們的window來訪問fatherson的值因此就報錯。所以閉包它就來了,讓本不應該訪問到的作用域現在可以訪問到,就是由於這個外部可以訪問,當外部沒有執行,所以函式作用域未作銷燬

開啟講解前面的程式碼

前面兩個,一個只是正常的巢狀,另一個是return出去。這兩就不講了。

直接講解第三個:

``` JS var falseSon function father() { var fatherVal = 'father' function son() { var sonVal = 'sonVal' console.log(fatherVal,' ',sonVal); //father sonVal } falseSon = son }

father() falseSon() ```

編譯器的步驟: (這裡可能有些人不理解為什麼先產生函式, 我就是這麼豪橫,你們就不理解著吧) 1. 先產生出father這個函式。 2. 宣告一個falseSon變數。 3. 執行father函式。 4. 再產生出son函式。 5. 在宣告fatherVal變數。 6. 對fatherVal變數進行賦值'father'。 7. 對falseSon變數進行賦值son。 8. 執行falseSon函式

從這裡我們看出在全域性作用域中我們本不應該能訪問到son的,因為son是屬於father的作用域。但是,就是因為惡人(閉包)找了東西把son複製給了falseSon出來給自己用,這個複製的很厲害擁有了son的所以技能(功能),然後因為有falseSon所以垃圾回收就沒有把father給收走

殺手鐗

``` JS function father(value) { setTimeout(function son(){ console.log(value); }, 1000) }

father('fatherValue') ``` 這裡跟上面的對比是不是有點難看出來它是一個閉包,只要我們找準誰擁有假孩子。

呔,我逮住了setTimeout,你這小子有假孩子(引數),這小子把假孩子藏起來了,還好我有火眼金金。 ``` js function father() { var fatherVal = 'father' function son() { var sonVal = 'sonVal' console.log(fatherVal,' ',sonVal); //father sonVal } result(son) }

function result(falseSon) { falseSon() }

father() 這不就是和它一樣嘛,setTimeout 就相當於 result。變身: js function father(value) { function son(){ console.log(value); } setTimeout(son, 1000) }

father('fatherValue') ``` 厲害吧,不講啦!就這樣吧。

Background (5).png

結論

Background (6).png

最後來個總結:

解釋者:想象下一個大盒子裡面包裹中盒子,中盒子包裹小盒子。我在大盒子外面本來拿不到小盒子,有了閉包我可能直接拿到小盒子

看者: 盒子盒子的,我看你就像一個盒子。

這次真溜溜。

Background (7).png

參考

《你不知道的JavaScript(上卷)》