let 声明变量

79 阅读3分钟

ES6 新增了 let 命令,用于声明变量。

1. let 所声明的变量只在 let 命令所在的代码块内有效。

{
  let a = 10
  var b = 1
}
a // ReferenceError: a is not defined
b // 1

2. for 循环的计数器很适合使用 let 命令。

for (let i = 0; i < 10;  i++) {
  // …
}
console.log(i)
// 报错 ReferenceError: i is not defined

以上代码中的计数器 i 只在 for 循环内有效,在循环体外引用就会报错。

var a = []
for (var i= 0; i < 10; i++) {
  a[i] = function () {
    console.log(i)
  }
}
a[6]()  // 10

解释:上面代码中,变量 i 是 var 声明的,在全局范围内都有效,所以全局只有一个变量 i 。每一次循环,变量 i 的值都会发生改变,而循环内,被赋给数组 a 的函数内部的 console.log(i) 中的 i 指向全局的 i 。也就是说,所有数组 a 的成员中的 i 指向的都是同一个 i ,导致运行时输出的是最后一轮的 i 值,也就是 10。

var a = []
for (let i= 0; i < 10; i++) {
  a[i] = function () {
    console.log(i)
  }
}
a[6]()  // 6

解释:上面代码中,变量 i 是 let 声明的,当前的 i 只在本轮循环有效。所以每一次循环的 i 其实都是一个新的变量,于是最后输出的是 6。JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,会在上一轮循环的基础上进行计算。

另外,for 循环还有一个特别之处,就是 设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc'
  console.log(i)
}
// abc
// abc
// abc
// 表明函数内部的变量 i 与循环变量  i 有各自单独的作用域。

3. 不存在变量提升

var 存在变量提升,即变量可以在声明之前使用,值为 undefined。

let 不存在变量提升,let 声明的变量一定要在声明后使用,否则便会报错。

// var 的情况
console.log(foo)  // 输出 undefined
var foo = 2

// let 的情况
console.log(bar)  // 报错 ReferenceError
let bar = 2

4. 暂时性死区

只要块级作用域内存在 let 命令,它所声明的变量就 “绑定” (binding)这个区域,不再受外部的影响。

var tmp = 123

if (true) {
  tmp = 'abc'  // ReferenceError
  let tmp
}

ES6 明确规定,如果区块中存在 let 和 const 命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。只要在声明之前就使用这些变量,就会报错。这在语法上称为 “暂时性死区”(temporal dead zone,简称 TDZ)。

4.1 typeof

“暂时性死区” 也意味着 typeof 不再是一个百分之百安全的操作。

typeof x;  // ReferenceError
let x;

但如果一个变量根本没有被声明,使用 typeof 反而不会报错。

typeof undeclared_variable   // undefined

4.2 隐蔽的“死区”

function bar(x = y, y = 2) {
  return [x, y]
}
bar()  
// 报错

因为参数 x 的默认值等于另一个参数 y,而此时 y 还没有声明,属于 “死区”。

如果 x 已经声明,y 的默认值是 x,就不会报错,如下:

function bar(x = 2, y = x) {
  return [x, y]
}
bar()  // [2, 2]

下面的代码也会报错,与 var 的行为不同:

// 不报错
var x = x

// 报错
let x = x
// ReferenceError: x is not defined

因为暂时性死区,使用 let 声明变量时,只要变量在还没有声明前使用,就会报错。

上面这个例子,在变量 x 的声明语句还没有执行完成前就尝试获取 x 的值,导致出现 “x 未定义” 的错误。

总之,暂时性死区的本质就是,只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

5. 不允许重复声明

let 不允许在相同作用域内重复声明同一个变量。

// 报错
function () {
  let a = 10
  var a = 1
}

// 报错
function () {
  let a = 10
  let a = 1
}

不能在函数内部重新声明参数。

function func(arg) {
  let arg  // 报错
}

function func(arg) {
  {
    let arg  // 不报错
  }
}