想要申明閉包,for輪迴是最常見的例子:
for(var i=1;i<=5;i++)
{
setTimeout(function timer(){
console.log(i);
},i*1000);
}
以我們所想,我們能夠以為他會輸出1~5,每秒一次,每次一個。
但現實上,這段代碼在運轉時會以每秒一次的頻次輸出五次6。
這是為何?
原因是耽誤函數會在輪迴完畢時才實行,事實上,當定時器運轉時縱然每一個迭代中實行的是setTimeout(…,0),一切的回調函數依然是在輪迴完畢后才會實行,因而會每次輸出一個6出來。
依據作用域的道理,現實情況:只管輪迴中的五個函數是在各個迭代中離別定義的,然則他們都被關閉在一個同享的全局作用域中,因而現實上只需一個i。
所以一切函數同享一個i的引用時,輪迴構造讓我們誤以為背地另有更龐雜的機制在器作用,但現實上啥都木有,假如將耽誤函數的回調反覆定義五次,完整不運用輪迴,那他同這段代碼是完整等價的。
處理方法以下:
我們先試一下:
for(var i=1;i<5;i++){
(function(){
setTimeout(function timer(){
console.log(i);
},i*1000);
})();
}
看似能夠,但現實也沒用,雖然如許寫我們有更多詞法作用域了,確實每一個耽誤函數都邑將IIFE在每次迭代中建立的作用域關閉起來。
假如作用域是空的,那末僅僅將他們舉行關閉是不夠的。細緻看一下,我們的IIFE只是一個什麼都沒有的空作用域,所以須要包括一點現實內容為我們所用。
他須要本身的變量,用來在每一個迭代中存儲i的值:
for(var i=0;i<=5;i++)
{
(function(){
var j=i;
setTimeout(function timer(){
console.log(j);
},j*1000);
})();
}
ok,他運轉如我們所願了!
能夠舉行革新:
for(var i=1;i<=5;i++)
{
(function{
setTimeout(function timer(){
console.log(j);
},j*1000);
})(i); //i能夠修改,只需你喜好
}
在迭代內運用IIFE會為每一個迭代都天生一個新的作用域,使得耽誤函數的回調能夠將新的作用域關閉在每一個迭代內部,每一個迭代中都邑包括一個具有準確值的變量供我們接見。
運用let處理
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);
}