天天看點

for循環裡的定時器以及 var 和 let 的使用

一道for循環的經典題目

for(var i = 0; i < 5; i++) {
    setTimeout(function () {
       console.log(' i = ' + i);
    });
}
console.log('a');
           

列印出:

for循環裡的定時器以及 var 和 let 的使用

看到這段代碼的人可能首先會想到會在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');
           

列印結果如下

for循環裡的定時器以及 var 和 let 的使用

為什呢 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開始到最後依次執行。

繼續閱讀