閉包確實是一個說爛了的概念,校招社招都會被問到,今天總結一番。
先下定義,閉包是函數和該函數的詞法做用域的組合。其實這個定義是比較教條的,能夠直白的理解爲閉包是一個函數,且這個函數使用了既沒在它內部聲明且不是它的參數的變量。
舉個栗子,html
function foo() { var a = 2; function bar() { console.log( a ); } return bar; } var baz = foo(); baz(); // 2
按照常理,foo函數在執行完畢以後會銷燬掉其內部的變量a,可是bar函數內部保持着對a的引用,因此經過調用foo()把bar的引用賦給了baz,運行baz()依然能夠打印出a。在這個栗子裏,函數bar以及它對變量a的引用就構成了閉包。面試
對於閉包和做用域的關係,個人理解是閉包其實就是做用域的延伸。
因爲在JavaScript中函數內部可使用函數外部的變量,全部有時候會不知不覺的產生閉包,假如在上面那個代碼片斷中,不容許函數內部使用函數外部的變量,閉包也就無從談起了。閉包
模擬私有變量和私有方法函數
var Dog = (function(){ var privateVal = 'dog' function doing(val) { console.log(privateVal + ' ' + val) } return { run: function(){ doing('run') }, bark: function(){ doing('bark') } } })() Dog.run() // dog run Dog.bark() // dog bark
能夠看到的是,run和bark這兩個閉包分享了同一個詞法做用域,且都引用了私有方法doing。這樣,咱們就能夠只向外暴露run和bark兩個公共接口而隱藏私有的變量和方法。code
或許這是面試中出現最多的問題...htm
for(var i = 1;i <= 5;i++) { setTimeout(function() { console.log(i) }, i*1000) } // 每隔一秒打印一個6,共打印5次
爲何事與願違,而不是按照咱們所想的依次的間隔1秒打印出1,2,3,4,5呢?首先,這段循環產生了5個閉包,並且最重要的是這5個閉包都處在同一個做用域中,也就是說它們引用的是同一個i,當for循環結束時,i變成了6。因此,5個匿名函數執行時會依次的去打印那同一個i,因此就打印出了5個6。
如何解決?
以前也說了讓這5個閉包處於不一樣的做用域且讓它們在各自的做用域中擁有它們各自的i便可。
可使用自執行函數來建立一個新的做用域blog
for(var i = 1;i <= 5;i++) { (function(k){ setTimeout(function() { console.log(k) }, k*1000) })(i) }
在這個代碼片斷中,每個setTimeout都處於一個獨立的做用域中,且都引用了它們各自的k,並非指向了外層做用域的i,因此就會打印出1,2,3,4,5
也可使用let接口
for(let i = 1;i <= 5;i++) { setTimeout(function() { console.log(i) }, i*1000) }
for 循環頭部的 let 不只將 i 綁定到了 for 循環的塊中,事實上它將其從新綁定到了循環的每個迭代中,確保使用上一個循環迭代結束時的值從新進行賦值。
其實使用let的本質是ip
for(let i = 1;i <= 5;i++) { let i = 上次迭代結束的i setTimeout(function() { console.log(i) }, i*1000) }
其實閉包就這麼多東西,並且主要是做用域的概念,做用域明白了,閉包也就明白了。
that's all, thank you.作用域
參考資料
深刻理解JavaScript系列-閉包
MDN-閉包
《你不知道的JavaScript-上卷》
「每日一題」JS 中的閉包是什麼?