What is the difference between “let” and “var”?
技术背景
在 JavaScript 中,var
是早期用于声明变量的关键字。然而,随着 ES6(ECMAScript 2015)的发布,引入了 let
和 const
两个新的关键字来声明变量。let
的出现主要是为了解决 var
在变量作用域和变量提升等方面带来的一些问题,使变量的使用更加安全和符合预期。
实现步骤
1. 作用域规则(Scoping rules)
var
的函数作用域:使用 var
声明的变量具有函数作用域,即变量在整个函数内部都是可见的。
1 2 3 4 5 6 7
| function testVar() { if (true) { var foo = 'foo'; } console.log(foo); } testVar();
|
let
的块级作用域:使用 let
声明的变量具有块级作用域,变量只在声明它的块(由 {}
包裹的代码块)内可见。
1 2 3 4 5 6 7
| function testLet() { if (true) { let bar = 'bar'; } console.log(bar); } testLet();
|
2. 变量提升(Hoisting)
var
的变量提升:使用 var
声明的变量会被提升到其所在作用域的顶部,并且在声明之前访问变量会得到 undefined
。
1 2 3 4 5 6
| function checkHoistingVar() { console.log(foo); var foo = 'Foo'; console.log(foo); } checkHoistingVar();
|
let
的变量提升:使用 let
声明的变量也会被提升,但在变量声明之前访问会导致 ReferenceError
,因为变量处于“暂时性死区”(Temporal Dead Zone,TDZ)。
1 2 3 4 5 6
| function checkHoistingLet() { console.log(foo); let foo = 'Foo'; console.log(foo); } checkHoistingLet();
|
3. 创建全局对象属性
var
创建全局对象属性:在全局作用域中使用 var
声明的变量会成为全局对象(在浏览器中是 window
对象)的属性。
1 2
| var foo = 'Foo'; console.log(window.foo);
|
let
不创建全局对象属性:在全局作用域中使用 let
声明的变量不会成为全局对象的属性。
1 2
| let bar = 'Bar'; console.log(window.bar);
|
4. 变量重声明
var
允许重声明:在严格模式下,使用 var
可以在同一作用域内对同一变量进行重声明。
1 2 3
| 'use strict'; var foo = 'foo1'; var foo = 'foo2';
|
let
不允许重声明:使用 let
在同一作用域内对同一变量进行重声明会抛出 SyntaxError
。
1 2 3
| 'use strict'; let bar = 'bar1'; let bar = 'bar2';
|
核心代码
作用域差异示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function testVar() { if (true) { var foo = 'foo'; } console.log(foo); } testVar();
function testLet() { if (true) { let bar = 'bar'; } console.log(bar); } testLet();
|
变量提升差异示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function checkHoistingVar() { console.log(foo); var foo = 'Foo'; console.log(foo); } checkHoistingVar();
function checkHoistingLet() { console.log(foo); let foo = 'Foo'; console.log(foo); } checkHoistingLet();
|
全局对象属性差异示例
1 2 3 4 5 6 7
| var foo = 'Foo'; console.log(window.foo);
let bar = 'Bar'; console.log(window.bar);
|
变量重声明差异示例
1 2 3 4 5 6 7 8 9
| 'use strict'; var foo = 'foo1'; var foo = 'foo2';
'use strict'; let bar = 'bar1'; let bar = 'bar2';
|
最佳实践
- 优先使用
let
:在大多数情况下,优先使用 let
来声明变量,因为它的块级作用域可以减少变量命名冲突的可能性,使代码更加安全和易于维护。 - 避免使用
var
声明全局变量:由于 var
声明的全局变量会成为全局对象的属性,可能会导致命名冲突,因此尽量避免使用 var
声明全局变量。 - 明确变量作用域:在编写代码时,要明确变量的作用域,避免不必要的变量泄漏。
常见问题
1. 为什么 var
在循环中会出现问题?
在使用 var
声明变量的循环中,由于 var
的函数作用域,所有的闭包都会引用同一个变量,导致循环结束后闭包中的变量值都是循环结束时的值。
1 2 3
| for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }
|
可以使用 let
来解决这个问题,因为 let
会为每次循环创建一个新的变量副本。
1 2 3
| for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }
|
2. let
和 const
有什么区别?
let
声明的变量可以重新赋值,而 const
声明的常量一旦赋值就不能再重新赋值。let
和 const
都具有块级作用域。
1 2 3 4 5
| let a = 1; a = 2;
const b = 1; b = 2;
|