深入学习循环中的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 声明模仿了闭包的做法来简化循环过程。

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