逗比的我,為你講“閉包”。
theme: simplicity-green highlight: a11y-dark
小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。
本想寫call、apply和bind的原理,寫著發現他們與this相關,我就去看this,然後發現this和作用域相關,最後就看到了閉包。這裡看的書籍《你不知道的JavaScript(上卷)》。
首先
我甩你一段程式碼: ``` 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') ``` 累死了,什麼!!這個你也知道,對不起!浪費了您幾分鐘時間。
你們都知道行了吧,我就皮,我寫給以後的自己看。既然這些都是閉包那麼它們有啥共同性呢?
正文
這裡先來稍微講解下作用域把,然後再引出閉包。
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的引數') ``` 這個程式碼大家都能看的懂,但是我還是畫了一張圖,大家就多浪費幾秒鐘看一眼。
這就是常規的作用域, 有點冒泡的感覺。
閉包
我再反手一段程式碼: ``` JS var wd = 'window' function father() { son() function son() { var sonValue = 'son' } }
son() console.log(sonValue); ```
這就是一段錯誤的程式碼
,從之前的作用域中我們是son
中可以拿
到window
和father
作用域上面的值
。這裡我們的window
來訪問father
和son
的值因此就報錯。所以閉包
它就來了,讓本不應該訪問
到的作用域現在可以訪問到
,就是由於這個外部可以訪問
,當外部沒有執行,所以函式作用域未作銷燬
。
開啟講解前面的程式碼
前面兩個,一個只是正常的巢狀,另一個是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') ``` 厲害吧,不講啦!就這樣吧。
結論
最後來個總結:
解釋者:想象下一個大盒子
裡面包裹中盒子
,中盒子包裹小盒子
。我在大盒子外面本來拿不到小盒子,有了閉包我可能直接拿到小盒子
。
看者: 盒子盒子的,我看你就像一個盒子。
這次真溜溜。
參考
《你不知道的JavaScript(上卷)》