1. 程式人生 > >for循環中let var的區別,從循環原理的角度去理解這個問題

for循環中let var的區別,從循環原理的角度去理解這個問題

分布 con 入門 spl 可能 斷點 play auto 一份

技術分享圖片

我在前一篇討論let與var區別的博客中,順帶一筆帶過了let與var在for循環中的不同表現,雖然解釋了是塊級作用域的影響,但具體是怎麽去影響的呢,我嘗試的去理解了下,這篇博客主要從for循環步驟拆分的角度去理解兩者的區別。

一、一個簡單的for循環問題與我思考後產生的問題

還是這段代碼,分別用var與let去聲明變量,得到的卻是完全不同的結果,為什麽?如果讓你把這個東西清晰的講給別人聽,怎麽去描述呢?

//使用var聲明,得到3個3
var a = [];
for (var i = 0; i < 3; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[
0](); //3 a[1](); //3 a[2](); //3 //使用let聲明,得到0,1,2 var a = []; for (let i = 0; i < 3; i++) { a[i] = function () { console.log(i); }; } a[0](); //0 a[1](); //1 a[2](); //2

再弄懂這個問題前,我們得知道for循環是怎麽執行的。首先,對於一個for循環,設置循環變量的地方是一個父作用域,而循環體代碼在一個子作用域內;別忘了for循環還有條件判斷,與循環變量的自增。

for循序的執行順序是這樣的:設置循環變量(var i = 0)

==》循環判斷(i<3) ==》滿足執行循環體 ==》循環變量自增(i++)

我們按照這個邏輯改寫上面的for循環,以第一個var聲明為例,結合父子作用域的特點,上面的代碼可以理解為:

{
  //我是父作用域
  let i = 0;
  if (0 < 3) {
    a[0] = function () {
      //我是子作用域
      console.log(i);
    };
  };
  i++; //為1
  if (1 < 3) {
    a[1] = function () {
      console.log(i);
    };
  };
  i
++; //為2 if (2 < 3) { a[2] = function () { console.log(i); }; }; i++; //為3 // 跳出循環 } //調用N次指向都是最終的3 a[0](); //3 a[1](); //3 a[2](); //3

那麽我們此時模擬的步驟代碼中的聲明方式var修改為let,執行代碼,發現輸出的還是3個3!WTF???

技術分享圖片

按照模糊的理解,當for循環使用let時產生了塊級作用域,每次循環塊級作用域中的 i 都相互獨立,並不想var那樣全程共用了一個。

但是有個問題,子作用域中並沒有let,何來的塊級作用域,整個循環也就父作用域中使用了一次let i = 0;子作用域哪裏來的塊級作用域?

技術分享圖片

請教了下百度的同學,談到了會不會是循環變量不止聲明了一次,其實自己也考慮到了這個問題,for循環會不會因為使用let而改變了我們常規理解的執行順序,自己又在子作用域用了let從而創造了塊級作用域?抱著僥幸的心理還是打斷點測試了一下:

技術分享圖片

可以看到,使用let還是一樣,聲明只有一次,之後就在後三個步驟中來回跳動了。

二、一個額外問題的暗示

如果說,在使用let的情況下產生了塊級作用域,每次循環的i都是獨立的一份,並不共用,那有個問題,第二次循環 i++ 自增時又是怎麽知道上一個塊級作用域中的 i 是多少的。這裏得到的解釋是從阮一峰ES6入門獲取的。

JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。

那這就是JS引擎底層實現的問題了,我還真沒法用自己的代碼去模擬去實現,我們分別截圖var與let斷點情況下作用域的分布。

首先是var聲明時,當函數執行時,只能在全局作用域中找到已被修改的變量i,此時已被修改為3

技術分享圖片

而當我們使用let聲明時,產生了block作用域,而且可以確定的是整個for循環let i只聲明了一次,但產生了三個塊級作用域,每個作用域中的 i 均不相同。

技術分享圖片

我的猜測,與JS引擎記錄 i 的變換進行循環自增而我們卻無法感知一樣,JS引擎在let的情況下,每次循環自己都創建了一個塊級作用域並塞到了for循環裏(畢竟子作用域裏沒用let),所以才有了三次循環三個獨立的塊級作用域以及三個獨立的 i。

這也只是我的猜測了,可能不對,如果有人能從JS底層實現給我解釋就更好了。

那麽說到這裏大概說完了,好像還是沒能很權威的說清楚,拆分步驟模擬for循環的方法無法解釋let的情況,也許還有我考慮不夠全面的地方,也歡迎有緣人指點。

雖然讀完本文得到了一個猜測,但是也將for執行機制又理清了一遍不是嗎。

for循環中let var的區別,從循環原理的角度去理解這個問題