因爲前段時間項目沒有那麼忙,而後我這人一天不看點啥就很是焦慮,因而二刷《你不知道的JavaScript》,如今讀到閉包,想着看完這一章節,寫點東西也是挺好的,因此有了下面的內容,若有不對的地方,敬請斧正,歡迎探討。閉包
咱們通常講到閉包,就會談到做用域,那麼做用域又分爲了函數做用域
和塊級做用域
,在這裏,咱們簡單的介紹一下這兩種做用域。異步
函數做用域是指,屬於這個函數的所有變量均可以在整個函數的範圍內使用及服用(事實上在嵌套的做用域中也可使用)。
咱們先來看一個例子函數
var a = 2; function foo() { var a = 3; console.log(a); // 3 } foo(); console.log(a); // 2
能夠看到,這種技術雖然能解決一些問題,可是也會致使其餘的問題。首先,必須聲明一個具名函數foo(),意味着foo這個名稱自己「污染」了所在的做用域。其次,必須顯式地(有隱式和顯式的區別,這裏暫且不表)經過函數名(foo())調用這個函數才能運行其中的代碼。
那麼咱們有沒有其餘的辦法呢,繼續往下看。工具
var a = 2 (function foo() { var a = 3; console.log(a); // 3 })(); console.log(a); // 2
比較一下這兩段代碼。第一段中foo被綁定在所在所用域中,能夠直接經過foo()來調用調用它。第二段中foo被綁定在函數表達式自身的函數中而不是所在做用域中。學習
在JavaScript中,並不支持塊做用域,可是咱們爲何還要說它,由於它的風格在JS開發中很常見。
在平常的開發或者學習工做中,咱們其實常常能見到相似塊做用域,思考如下代碼:code
for(var i=0; i<6; i++){ console.log(i); }
在for循環的頭部直接定義了變量 i,一般是由於只想在for循環內部的上下文中使用 i,而忽略了 i 會被綁定在外部做用域(函數或全局)中的事實。當使用var
時,它寫在哪裏都是同樣的,由於它們最終都會屬於外部做用域。
塊做用域是一個用來對以前的最小受權原則進行擴展的工具,將代碼從在函數中隱藏信息擴展爲在塊中隱藏信息。事件
繼續思考下面代碼:ip
function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); // 2
看到了嗎,這就是閉包的效果。
函數bar()
的詞法做用域可以訪問foo()
的內部做用域。而後將bar()
函數自己看成一個值類型進行傳遞。
在foo()
執行後,其返回值賦值給變量baz
並調用baz()
,實際上只是經過不一樣的標識符引用調用了內部的函數bar()
。
咱們知道,JavaScript引擎有垃圾回收器用來釋放再也不使用的內存空間。而閉包卻能阻止這件事情發生。事實上內部做用域依然存在,而沒有被回收。
因爲bar()
的聲明位置使它擁有涵蓋foo()
內部做用域的閉包,使得該做用域可以一直存在,以供bar()
在以後進行引用。
再看下面例子內存
function foo() { var a = 2; function baz() { console.log(a); // 2 } bar(baz); } function bar(fn) { fn(); // 這是閉包 } foo();
不管經過何種手段將內部函數傳遞到所在的詞法做用域外,它都會持有對原始定義做用域的引用,不管在何處執行這個函數都會使用閉包。作用域
OK,本質上不管什麼時候何地,若是將(訪問它們各自詞法做用域的)函數看成第一級的值類型並處處傳遞,你就能看到閉包了。在定時器、事件監聽器、Ajax請求或其餘異步或同步任務中,只要使用了回調函數
,實際上就是在使用閉包。
先看看最多見的for循環。
for(var i=1; i<=5; i++) { setTimeout(function timer(){ console.log(i); }, i*1000) }
你以爲最後會輸出什麼,每秒輸出一次,分別輸出1~5?
那就錯啦,實際上,它是會每秒輸出一次,但輸出~對,就是66666。
爲何?
延遲函數的回調會在循環結束時才執行,而循環結束的條件就是i再也不<=5。當定時器運行時,即便每一個迭代中執行的是setTimeout(...,0),全部的回調函數依然是在循環結束後才被執行,因此每次都輸出6。
根據做用域的原理,儘管循環中的五個函數是在各個迭代中分別定義的,可是它們都被封閉在一個共享的全局做用域中,所以只有一個i。
知道緣由以後,咱們能夠對代碼進行一些改造,看看有沒有好事發生。
foo(var i=1; i<=5; i++) { (function (j) { setTimeout(function timer() { console.log(j); }, j*1000); })(i); }
Fine,咱們終於改造好了,擁有了更多的詞法做用域。在迭代中使用當即執行函數(IIFE)會爲每一個迭代都生成一個新的做用域,使得延遲函數的回調能夠將新的做用域封閉在每一個迭代內部,每一個迭代中都會有一個具備正確值的變量。
到這裏,小菊花課堂之JavaScript閉包的內容就告一段落啦,感謝各位能耐心看到這裏。
此時是0點52分,時候也不早了,該洗洗睡啦。
see u ~ again