What is the difference between "let" and "var"?

What is the difference between “let” and “var”?

技术背景

在 JavaScript 中,var 是早期用于声明变量的关键字。然而,随着 ES6(ECMAScript 2015)的发布,引入了 letconst 两个新的关键字来声明变量。let 的出现主要是为了解决 var 在变量作用域和变量提升等方面带来的一些问题,使变量的使用更加安全和符合预期。

实现步骤

1. 作用域规则(Scoping rules)

  • var 的函数作用域:使用 var 声明的变量具有函数作用域,即变量在整个函数内部都是可见的。
1
2
3
4
5
6
7
function testVar() {
if (true) {
var foo = 'foo';
}
console.log(foo); // 输出: 'foo'
}
testVar();
  • let 的块级作用域:使用 let 声明的变量具有块级作用域,变量只在声明它的块(由 {} 包裹的代码块)内可见。
1
2
3
4
5
6
7
function testLet() {
if (true) {
let bar = 'bar';
}
console.log(bar); // 抛出 ReferenceError
}
testLet();

2. 变量提升(Hoisting)

  • var 的变量提升:使用 var 声明的变量会被提升到其所在作用域的顶部,并且在声明之前访问变量会得到 undefined
1
2
3
4
5
6
function checkHoistingVar() {
console.log(foo); // 输出: undefined
var foo = 'Foo';
console.log(foo); // 输出: 'Foo'
}
checkHoistingVar();
  • let 的变量提升:使用 let 声明的变量也会被提升,但在变量声明之前访问会导致 ReferenceError,因为变量处于“暂时性死区”(Temporal Dead Zone,TDZ)。
1
2
3
4
5
6
function checkHoistingLet() {
console.log(foo); // 抛出 ReferenceError
let foo = 'Foo';
console.log(foo); // 输出: 'Foo'
}
checkHoistingLet();

3. 创建全局对象属性

  • var 创建全局对象属性:在全局作用域中使用 var 声明的变量会成为全局对象(在浏览器中是 window 对象)的属性。
1
2
var foo = 'Foo';
console.log(window.foo); // 输出: 'Foo'
  • let 不创建全局对象属性:在全局作用域中使用 let 声明的变量不会成为全局对象的属性。
1
2
let bar = 'Bar';
console.log(window.bar); // 输出: undefined

4. 变量重声明

  • var 允许重声明:在严格模式下,使用 var 可以在同一作用域内对同一变量进行重声明。
1
2
3
'use strict';
var foo = 'foo1';
var foo = 'foo2'; // 没问题,'foo1' 被 'foo2' 替换
  • let 不允许重声明:使用 let 在同一作用域内对同一变量进行重声明会抛出 SyntaxError
1
2
3
'use strict';
let bar = 'bar1';
let bar = 'bar2'; // 抛出 SyntaxError: Identifier 'bar' has already been declared

核心代码

作用域差异示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// var 的函数作用域
function testVar() {
if (true) {
var foo = 'foo';
}
console.log(foo); // 输出: 'foo'
}
testVar();

// let 的块级作用域
function testLet() {
if (true) {
let bar = 'bar';
}
console.log(bar); // 抛出 ReferenceError
}
testLet();

变量提升差异示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// var 的变量提升
function checkHoistingVar() {
console.log(foo); // 输出: undefined
var foo = 'Foo';
console.log(foo); // 输出: 'Foo'
}
checkHoistingVar();

// let 的变量提升
function checkHoistingLet() {
console.log(foo); // 抛出 ReferenceError
let foo = 'Foo';
console.log(foo); // 输出: 'Foo'
}
checkHoistingLet();

全局对象属性差异示例

1
2
3
4
5
6
7
// var 创建全局对象属性
var foo = 'Foo';
console.log(window.foo); // 输出: 'Foo'

// let 不创建全局对象属性
let bar = 'Bar';
console.log(window.bar); // 输出: undefined

变量重声明差异示例

1
2
3
4
5
6
7
8
9
// var 允许重声明
'use strict';
var foo = 'foo1';
var foo = 'foo2'; // 没问题,'foo1' 被 'foo2' 替换

// let 不允许重声明
'use strict';
let bar = 'bar1';
let bar = 'bar2'; // 抛出 SyntaxError: Identifier 'bar' has already been declared

最佳实践

  • 优先使用 let:在大多数情况下,优先使用 let 来声明变量,因为它的块级作用域可以减少变量命名冲突的可能性,使代码更加安全和易于维护。
  • 避免使用 var 声明全局变量:由于 var 声明的全局变量会成为全局对象的属性,可能会导致命名冲突,因此尽量避免使用 var 声明全局变量。
  • 明确变量作用域:在编写代码时,要明确变量的作用域,避免不必要的变量泄漏。

常见问题

1. 为什么 var 在循环中会出现问题?

在使用 var 声明变量的循环中,由于 var 的函数作用域,所有的闭包都会引用同一个变量,导致循环结束后闭包中的变量值都是循环结束时的值。

1
2
3
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 输出: 3 3 3
}

可以使用 let 来解决这个问题,因为 let 会为每次循环创建一个新的变量副本。

1
2
3
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 输出: 0 1 2
}

2. letconst 有什么区别?

  • let 声明的变量可以重新赋值,而 const 声明的常量一旦赋值就不能再重新赋值。
  • letconst 都具有块级作用域。
1
2
3
4
5
let a = 1;
a = 2; // 没问题

const b = 1;
b = 2; // 抛出 TypeError: Assignment to constant variable.

What is the difference between "let" and "var"?
https://119291.xyz/posts/2025-05-07.difference-between-let-and-var/
作者
ww
发布于
2025年5月7日
许可协议