深入學習循環中的let

let 和 const的特性

1.不會被提升

  console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
  console.log(b) // Uncaught ReferenceError: Cannot access 'b' before initialization
  let a = 1
  const b =2

2.不可重複聲明

  var a = 1
  const a = 2 // Uncaught SyntaxError: Identifier 'a' has already been declared
  var b = 1
  let b = 2 // Uncaught SyntaxError: Identifier 'b' has already been declared

3.不綁定全局作用域

  let a = 1;
  const b = 2
  console.log(window.a); // undefined
  console.log(window.b); // undefined

拓展延申(開發中,要知道不綁定全局作用域就夠了,有興趣的可以研究下拓展部分)
問:既然不綁定在window上,那它們究竟綁定在哪呢?
要想知道答案,得先了解JS的詞法環境,可以參考這篇文章,下面引用別人的一段話來稍作解釋

ES6 開始,有別於全局環境記錄,有一個單獨的聲明環境記錄,它關聯一個詞法環境對象 (Lexical Environment Object) 來存儲那些不是通過 var function 關鍵字進行的標識符聲明,並且它們都是不可見的。

簡而言之

  • varfunction變量的聲明和保存是在變量環境組件中的,即VariableEnvironment
  • letconst變量的聲明和保存以及外部環境引用是在詞法環境組件中的,即LexicalEnvironment

4.只在代碼塊內有效

  {
    var a = 0;
    let b = 1;
  }
  console.log(a) // 0
  console.log(b) // Uncaught ReferenceError: b is not defined

經典for循環

  var arr = []
  for (var i = 0; i < 5; i++) {
    arr[i] = function () {
      console.log(i)
    }
  }
  arr[3]();  // 打印結果:5

因爲var聲明的變量,會被提升,上面的代碼等價於下面的代碼,變量i在循環結束後被賦值爲5

  var arr = []
  var i
  for (i = 0; i < 5; i++) {
    arr[i] = function () {
      console.log(i)
    }
  }
  arr[3]();  // 打印結果:5

想要輸出結果爲3的話,ES6的let爲此問題提供瞭解決方法

  var arr = []
  for (let i = 0; i < 5; i++) {
    arr[i] = function () {
      console.log(i)
    }
  }
  arr[3]();  // 打印結果:3

那爲什麼let它就能解決呢?
一個被提到的比較多的原因是,let變量不提升,可就算不提升,最後i還是變成5,輸出也應該是5啊?!!

很多文章中都對這一問題有過解答

如果要追究這個問題,就要拋棄掉之前所講的這些特性!這是因爲 let 聲明在循環內部的行爲是標準中專門定義的,在早期的 let 實現中本就不包含這一行爲,後來專門爲這一問題加入這一行爲

既然是專門定義的,就肯定有其特殊之處,那特殊在哪呢?

在 for 循環中使用 let 和 var,底層會使用不同的處理方式。
那麼當使用 let 的時候底層到底是怎麼做的呢?
簡單的來說,就是在 for (let i = 0; i < 3; i++) 中,即圓括號之內建立一個隱藏的作用域
然後每次迭代循環時都創建一個新變量,並以之前迭代中同名變量的值將其初始化。

這樣對於下面這樣一段代碼

  var arr = []
  for (let i = 0; i < 5; i++) {
    arr[i] = function () {
      console.log(i)
    }
  }
  arr[3]();  // 打印結果:3

就相當於:

// 僞代碼
(let i = 0) {
    arr[0] = function() {
        console.log(i)
    };
}

(let i = 1) {
    arr[1] = function() {
        console.log(i)
    };
}

(let i = 2) {
    arr[2] = function() {
        console.log(i)
    };
};

當執行函數的時候,根據詞法作用域就可以找到正確的值,其實你也可以理解爲 let 聲明模仿了閉包的做法來簡化循環過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章