一道for循環的經典題目
for(var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(' i = ' + i);
});
}
console.log('a');
列印出:
看到這段代碼的人可能首先會想到會在for裡循環5次 i ,然後才會列印下面的字元串a,for循環裡的列印結果會是 0 1 2 3 4 而不是出現5個 i = 5 這種意想不到的結果
但是這段代碼實際跑起來的結果和我們預想的不一樣。為什麼會這樣呢,其實這個問題并不難,遇到了,下次基本就不會錯了,但是這個簡單的代碼内包含很多的js知識點。
JavaScript語言的一大特點就是單線程,也就是說同一時間隻能做一件事情,所有的任務都需要等待上一個任務完成之後才會執行下一個任務。
.
于是出現了同步和異步。
a、同步就是相當于你放一首歌,他會一直跑下去,如果中間斷了,他也不會去播放下一首歌,當通知你上一首播放完畢了,才會播放下一首個。播放歌就像等于進入了主線程,第二首歌就會在任務隊列裡等待主線程的執行。
b、異步就相當于同時放兩首歌,一首歌中斷了,不會對另一首歌有影響。另一首歌還會一直播放下去。相當于兩首歌都在任務隊列裡。當上面有同步任務在主線程内執行完畢,異步任務就可以進入主線程執行任務。
隻要主線程的任務空了,就會去找任務隊列的任務去執行,JavaScript就是在不斷重複這個過程的運作機制。
任務隊列中的事件,主要指定了回調函數,這些事件發生的時候就會進入任務隊列等待主線程的執行完畢。
也就是說,JS會任務setTimeout是一個異步操作,當console.log(‘a’)這個同步任務執行完畢之後,才會讓他setTimeout執行
總結來說:定時器不是同步的,他會自動的進入任務隊列,等待同步任務的執行完畢才會執行,這也就是 為什麼會先列印出 a 字元串 在列印出5次i = 5的原因。
那麼為什麼不是 0 1 2 3 4 a呢 ,是因為同步代碼執行完畢之後,i 在for循環裡 i++ 加到5停止執行 ,是以 i 已經變成了5 ,這時候循環已經結束了。
于是就可以想象成這個樣子,以便了解。
會先for循環,i = 5 然後列印 a 然後在列印 5 次 i
for (var i = 0; i < 5; i++) {
}
console.log('a');
setTimeout(function () {
console.log('i = ' + i);
});
setTimeout(function () {
console.log('i = ' + i);
});
setTimeout(function () {
console.log('i = ' + i);
});
setTimeout(function () {
console.log('i = ' + i);
});
setTimeout(function () {
console.log('i = ' + i);
});
但是還是想要結果列印出 0 1 2 3 4 呢 該怎麼辦?
可以使用ES6新增聲明 let
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log('i = ' + i);
});
}
console.log('a');
列印結果如下
為什呢 let 可以呢?
先看以 let 和 var 兩種聲明方式的差別
1、沒有變量提升,也就說必須提前聲明才能使用,
console.log(a);
let a = 666
//會報錯Uncaught ReferenceError: Cannot access 'a' before initialization
2、不能重複聲明
let a = 666
let a = 555555
//報錯Uncaught SyntaxError: Identifier 'a' has already been declared
3、塊級作用域
{
var i = 5;
}
console.log(i); // 5
```javascript
{
let i = 5; // i變量隻在 花括号内有效
}
console.log(i); // Uncaught ReferenceError: i is not defined
回到主題,因為let存在的塊級作用域 ,for循環和定時器共享同一個作用域的同一個變量,但是let在循環變量中還有一個特殊功能:
每一次循環都會重新聲明變量 i ,随後每一個循環都會使用上一個循環時結束的值來初始化這個變量 i 。let非常适合用于 for循環内部的塊級作用域。JS中的for循環體比較特殊,每次執行都是一個全新的獨立的塊作用域,用let聲明的變量傳入到 for循環體的作用域後,不會發生改變,不受外界的影響。
i 雖然在全局作用域聲明,但是在for循環體局部作用域中使用的時候,變量會被固定,不受外界幹擾。
i 是循環體内局部作用域,不受外界影響。
也就是說,他也是同步任務 ,完成for的循環之後 ,定時器裡的變量不會使用for循環之後的 i 的值,它會重新使用 i 變量 ,從0開始到最後依次執行。