JavaScript 中 var、let、const 详解
本文详细对比了JS中
var,let, 和const在作用域、变量提升、重复声明和重新赋值方面的核心区别,并给出了最佳实践。
我们来详细解析一下 JavaScript 中 var, let, 和 const 这三个关键字的区别。这是 JS 中一个非常基础且重要的概念,尤其是在 ES6 (let 和 const 被引入) 之后。
简单来说:
var: ES5 的变量声明方式,存在一些问题(如变量提升、无块级作用域)。let: ES6 引入的块级作用域变量声明方式,修复了var的主要问题。const: ES6 引入的块级作用域常量声明方式,声明后其值(内存地址)不能再改变。
下面我们通过一个对比表格和详细解释来深入理解。
核心区别速查表
| 特性 | var |
let |
const |
|---|---|---|---|
| 作用域 (Scope) | 函数作用域 (Function Scope) | 块级作用域 (Block Scope) | 块级作用域 (Block Scope) |
| 变量提升 (Hoisting) | 会,且初始化为 undefined |
会,但存在“暂时性死区” | 会,但存在“暂时性死区” |
| 重复声明 | 允许在同一作用域内重复声明 | 不允许在同一作用域内重复声明 | 不允许在同一作用域内重复声明 |
| 重新赋值 | 允许 | 允许 | 不允许 (基本类型的值,或对象的引用) |
| 声明时初始化 | 可不初始化 (默认为 undefined) |
可不初始化 (默认为 undefined) |
必须在声明时初始化 |
| 全局对象属性 | 在全局作用域声明时,会成为 window 对象的属性 |
在全局作用域声明时,不会成为 window 对象的属性 |
在全局作用域声明时,不会成为 window 对象的属性 |
详细解释与代码示例
1. 作用域 (Scope)
这是最核心的区别。
var(函数作用域)var声明的变量只在它所在的函数内部有效。如果在函数外声明,它就是全局变量。它没有块级作用域的概念。javascriptfunction testVar() { if (true) { var message = "Hello"; // message 在整个函数内都有效 } console.log(message); // 输出 "Hello",变量"泄露"到了块外部 } testVar(); // console.log(message); // 报错: message is not defined,因为它在函数外部不可见在
for循环中,这个问题尤其明显:javascriptfor (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // 会连续输出 3, 3, 3 }, 100); } // 因为循环结束时 i 的值是 3,而 setTimeout 的回调函数在循环结束后才执行, // 它们共享同一个函数作用域下的变量 i。let和const(块级作用域)let和const声明的变量只在它们所在的代码块({}包裹的范围)内有效。javascriptfunction testLet() { if (true) { let message = "Hello"; const year = 2023; console.log(message); // 输出 "Hello" } // console.log(message); // 报错: message is not defined // console.log(year); // 报错: year is not defined } testLet();用
let解决for循环问题:javascriptfor (let i = 0; i < 3; i++) { // 每次循环都会创建一个新的块级作用域,i 的值被“锁”在当前循环中 setTimeout(function() { console.log(i); // 会依次输出 0, 1, 2 }, 100); }
2. 变量提升 (Hoisting) 与 暂时性死区 (TDZ)
varvar声明的变量会被提升到其作用域的顶部,并被初始化为undefined。这意味着你可以在声明之前访问它,不会报错,只是值为undefined。javascriptconsole.log(myVar); // 输出: undefined var myVar = "I am hoisted"; console.log(myVar); // 输出: "I am hoisted"上面的代码在执行时,等同于:
javascriptvar myVar; // 提升并初始化为 undefined console.log(myVar); myVar = "I am hoisted"; console.log(myVar);let和constlet和const声明的变量也会被提升,但它们不会被初始化。在代码运行到声明语句之前,任何对该变量的访问都会导致ReferenceError。从作用域顶部到声明语句的这部分区域被称为“暂时性死区” (Temporal Dead Zone, TDZ)。javascript// console.log(myLet); // 报错: ReferenceError: Cannot access 'myLet' before initialization let myLet = "I am not initialized"; console.log(myLet); // 输出: "I am not initialized" // console.log(myConst); // 同样会报错 const myConst = "Me too";
3. 重复声明 (Re-declaration)
var: 允许在同一个作用域内重复声明同一个变量,后面的声明会覆盖前面的。javascriptvar name = "Alice"; var name = "Bob"; // 合法,name 的值现在是 "Bob" console.log(name); // 输出: "Bob"let和const: 在同一个作用域内,不允许重复声明同一个变量,否则会抛出SyntaxError。javascriptlet age = 30; // let age = 31; // 报错: SyntaxError: Identifier 'age' has already been declared const country = "USA"; // const country = "Canada"; // 同样会报错
4. 重新赋值 (Re-assignment)
var和let: 允许被重新赋值。javascriptvar a = 1; a = 2; // 可以 let b = 1; b = 2; // 也可以const: 声明的是一个常量。一旦声明,就不能再被重新赋值。javascriptconst PI = 3.14; // PI = 3.14159; // 报错: TypeError: Assignment to constant variable.重要注意事项: 当
const声明的是一个对象或数组时,它保护的是变量的引用(内存地址),而不是引用的值(对象或数组的内容)。这意味着你不能让这个变量指向另一个对象,但你可以修改该对象内部的属性。javascriptconst person = { name: "Alice" }; // 合法操作:修改对象内部的属性 person.name = "Bob"; person.age = 25; console.log(person); // 输出: { name: "Bob", age: 25 } // 错误操作:尝试将 person 指向一个新对象 // person = { name: "Charlie" }; // 报错: TypeError: Assignment to constant variable.
总结与最佳实践
在现代 JavaScript 开发中,遵循以下原则是最佳实践:
- 优先使用
const: 默认使用const来声明所有变量。这能让你的代码更具可预测性,因为你知道这个变量的引用不会改变。 - 当需要重新赋值时,才使用
let: 只有当你明确知道一个变量的值在未来需要被改变时(例如,循环中的计数器或一个状态标志),才使用let。 - 避免使用
var: 在新的代码中完全避免使用var。它带来的作用域和变量提升问题很容易导致难以察觉的 bug。let和const是更安全、更现代的选择。