JavaScript 中声明变量的关键字 var 与 let 的区别

578 阅读4分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

声明变量的关键字块级作用域通过 window 的属性访问重复定义已声明的变量进行变量提升
var
let不能不能不会

1. 块级作用域:

使用 var 声明的变量不具有块作用域。这种变量的作用域仅限于包含函数的函数体,无论它们在函数中嵌套的层次有多深。

{
  var a1 = 1
  console.log(a1) // 1
} // 这边的这对花括号其实是多余的(Block is redundant(块是多余的)),这里只是为了对比说明 var 和 let 的区别
// 用 var 声明的变量 a1 没有块级作用域,在块外仍可以访问
console.log(a1) // 1

{
  let a2 = 1
  console.log(a2) // 1
}
// 用 let 声明的变量 a2 在块级作用域外无法访问
console.log(a2) // 报错:Uncaught ReferenceError: a2 is not defined

2. 通过 window 的属性访问

如果在函数体外部使用 var,则会声明一个全局变量。但通过 var 声明的全局变量与通过 let 声明的全局变量有一个重要区别。通过 var 声明的全局变量被实现为全局对象的属性。全局对象可以通过 globalThis 引用。因此,如果你在函数外部写了 var x = 2;,就相当于写了 globalThis.x = 2;。不过要注意,这么类比并不完全恰当。因为通过全局 var 创建的这个属性不能使用 delete 操作符删除。而通过 letconst 声明的全局变量和常量不是全局对象的属性。

var b = 3 // 用 var 定义全局变量b
let c = 4 // 用 let 定义全局变量c

console.log(b, c) // 3 4
// 用 var 定义的全局变量能通过 window 的属性去访问
// 用 let 定义的全局变量不能通过 window 的属性去访问(访问结果为 undefined)
console.log(window.b, window.c) // 3 undefined

3. 重复定义已声明的变量

与通过 let 声明的变量不同,使用 var 多次声明同名变量是合法的。而且由于 var 变量具有函数作用域而不是块作用域,这种重新声明实际上是很常见的。变量 i 经常用于保存整数值,特别是经常用作 for 循环的索引变量。在有多个 for 循环的函数中,每个循环通常都以 for(var i = 0; ... 开头。因为 var 并不会把这些变量的作用域限定在循环体内,每次循环都会(无害地)重新声明和重新初始化同一个变量。

var b = 3 // 用 var 定义全局变量b
let c = 4 // 用 let 定义全局变量c

// 已经(用 var)声明过的变量能用 var 重复声明
var b = 6
console.log(b) // 6
var c = 6 // 重新声明之前用 var 声明的变量
console.log(c) // 报错:Identifier 'c' has already been declared

// 已经声明过的变量不能用 let 重复声明
let b = 6 // 报错:Uncaught SyntaxError: Identifier 'b' has already been declared
console.log(b) 
let c = 6 // 报错:Uncaught SyntaxError: Identifier 'c' has already been declared
console.log(c) 

在使用 var 重复声明变量时,如果没有进行赋值,那么该变量会使用最近一次声明且赋值时的值:

var b = 3;
var b;
var b = 8;
var b;
console.log(b) // 8

4. 变量提升

var 声明的一个最不同寻常的特性是作用域提升(hoisting)。在使用 var 声明变量时,该声明会被提高(或提升)到包含函数的顶部。但变量的初始化仍然在代码所在位置完成,只有变量的定义转移到了函数顶部。因此对使用 var 声明的变量,可以在包含函数内部的任何地方使用而不会报错。如果初始化代码尚未运行,则变量的值可能是 undefined,但在初始化之前是可以使用变量而不报错的(这会成为一个 bug 来源,也是 let 要纠正的一个最重要的错误特性。如果使用 let 声明了一个变量,但试图在 let 语句运行前使用该变量则会导致错误,而不是得到 undefined 值)。

function test () {
  var a = 3
  if (a === 3) {
    var b = 4 // 此处会进行变量提升
    console.log('b = ' + b)
  }
  console.log(b) // 不会报错
}
// 上面函数 test() 中的变量 b 因为是用 var 定义的,浏览器 js 引擎会对其进行变量提升,最终会变成如下效果:
function test () {
  var a = 3
  var b // b 变量提升后,就跑到了这个位置
  if (a === 3) {
    b = 4 // b 变量原来的位置
    console.log('b = ' + b)
  }
  console.log(b)
}

// 而如果是 let,则不会进行变量提升
function test () {
  var a = 3
  if (a === 3) {
    let b = 4 // 变量 b 是 let 定义的,不会进行变量提升,所以作用域被限制在本块中
    console.log('b = ' + b)
  }
  console.log(b) // 报错:Uncaught ReferenceError: b is not defined
}