想要說明閉包,for循環是最多見的例子:閉包
for(var i=1;i<=5;i++) { setTimeout(function timer(){ console.log(i); },i*1000); }
以咱們所想,咱們可能認爲他會輸出1~5,每秒一次,每次一個。
但實際上,這段代碼在運行時會以每秒一次的頻率輸出五次6。函數
這是爲何?code
緣由是延遲函數會在循環結束時才執行,事實上,當定時器運行時即便每一個迭代中執行的是setTimeout(...,0),全部的回調函數依然是在循環結束後纔會執行,所以會每次輸出一個6出來。作用域
根據做用域的原理,實際狀況:儘管循環中的五個函數是在各個迭代中分別定義的,可是他們都被封閉在一個共享的全局做用域中,所以實際上只有一個i。
因此全部函數共享一個i的引用時,循環結構讓咱們誤認爲背後還有更復雜的機制在器做用,但實際上啥都木有,若是將延遲函數的回調重複定義五次,徹底不使用循環,那他同這段代碼是徹底等價的。回調函數
解決方法以下:
咱們先試一下:io
for(var i=1;i<5;i++){ (function(){ setTimeout(function timer(){ console.log(i); },i*1000); })(); }
看似能夠,但實際也沒用,雖然這樣寫咱們有更多詞法做用域了,的確每一個延遲函數都會將IIFE在每次迭代中建立的做用域封閉起來。
若是做用域是空的,那麼僅僅將他們進行封閉是不夠的。仔細看一下,咱們的IIFE只是一個什麼都沒有的空做用域,因此須要包含一點實際內容爲咱們所用。console
他須要本身的變量,用來在每一個迭代中存儲i的值:for循環
for(var i=0;i<=5;i++) { (function(){ var j=i; setTimeout(function timer(){ console.log(j); },j*1000); })(); }
ok,他運行如咱們所願了!function
能夠進行改進:變量
for(var i=1;i<=5;i++) { (function{ setTimeout(function timer(){ console.log(j); },j*1000); })(i); //i能夠改動,只要你喜歡 }
在迭代內使用IIFE會爲每一個迭代都生成一個新的做用域,使得延遲函數的回調能夠將新的做用域封閉在每一個迭代內部,每一個迭代中都會包含一個具備正確值的變量供咱們訪問。
for循環的let聲明還會有一個特殊行爲,這個行爲之處變量在循環過程當中不知被聲明一次,每次迭代都會聲明,隨後的每一個迭代都會使用上一個迭代結束時的值來初始化這個變量。
for(var i=1;i<=5;i++) { let j=i; //閉包 setTimeout(function timer(){ console.log(j); },j*1000); } 下面是進化版 for(let i;i<=5;i++) { setTimeout(function timer(){ console.log(i); },i*1000); }