JS Let 与 Const
理解 let/const/var 的区别和作用域 · 难度:入门 · +10XP
JavaScript Let 与 Const 详解
在 ES6(ECMAScript 2015)之前,JavaScript 只有一种声明变量的方式:var。ES6 引入了 let 和 const,彻底解决了 var 的众多痛点。理解这三者的区别是每个 JavaScript 初学者的必修课,也是面试中的高频考点。
var 的五大问题
var 存在五个主要缺陷:函数作用域而非块级作用域、变量提升容易产生困惑、允许重复声明、全局作用域下会挂载到 window 对象、以及在循环中产生经典的闭包陷阱。这些问题在大型项目中极易导致难以排查的 bug。
// var 的块级作用域问题
if (true) {
var x = 10;
}
console.log(x); // 10 —— 块外仍能访问!其他语言中这是不可能的
// var 的变量提升(hoisting)
console.log(y); // undefined,而非报错 ReferenceError
var y = 5;
// var 允许重复声明(let/const 不允许)
var z = 1;
var z = 2; // 完全没问题,但极易造成混淆
let —— 块级作用域的变量
let 声明的变量只在当前代码块(由花括号 {} 界定)内有效,不可重复声明,存在暂时性死区而不会被提升为 undefined。let 是编写现代 JavaScript 时声明可变变量的推荐方式。
// let 块级作用域
if (true) {
let a = 100;
console.log(a); // 100
}
// console.log(a); // ReferenceError: a is not defined
// let 解决循环闭包经典问题
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出: 0, 1, 2(每次迭代都是新的绑定)
// 如果用 var,三次输出都是 3
const —— 常量声明
const 用于声明常量,必须在声明时赋值,且不可重新赋值。但要特别注意:const 保证的是引用不可变,而非值不可变。如果 const 指向对象或数组,其属性或元素仍然可以修改。
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable
// const 对象/数组的内容仍可修改
const arr = [1, 2, 3];
arr.push(4); // 完全合法!
arr[0] = 99; // 也合法 —— 修改的是数组内容,不是引用
// arr = [5, 6]; // 报错!试图改变引用
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 合法 —— 修改属性
obj.age = 25; // 合法 —— 新增属性
// obj = {}; // 报错!
三者全面对比
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 提升(Hoisting) | 提升并初始化为 undefined | 提升但不初始化(TDZ) | 提升但不初始化(TDZ) |
| 可重复声明 | 允许 | 不允许 | 不允许 |
| 可重新赋值 | 允许 | 允许 | 不允许 |
| 声明时必须赋值 | 可选 | 可选 | 必须 |
| 全局下挂载到 window | 是 | 否 | 否 |
| 适用场景 | 不再推荐使用 | 需要重新赋值的变量 | 不变的常量/配置 |
暂时性死区(TDZ)
let 和 const 声明的变量在声明之前不可访问,从块开始到声明语句之间的这段区域称为"暂时性死区"(Temporal Dead Zone)。这是 ES6 专门设计的机制,用于防止在变量未初始化时意外使用它,比 var 的 undefined 初始化更加安全。
// TDZ 演示
{
// console.log(z); // ReferenceError: Cannot access 'z' before initialization
let z = 42;
console.log(z); // 42 —— 声明之后才可访问
}
// typeof 也不再安全
// console.log(typeof undeclaredLet); // ReferenceError
console.log(typeof undeclaredVar); // "undefined"(var 时代的老行为)
最佳实践建议
推荐的声明策略(按优先级):
- 默认使用 const:大多数变量在初始化后不需要重新赋值,用 const 表达"这个绑定不会变"的意图。
- 需要重新赋值时用 let:循环计数器、累加器、状态变量等确实需要改变的场景。
- 完全避免 var:在新代码中永远不要使用 var,它的行为与现代 JavaScript 的预期不一致。
- 基础练习:用 let 声明变量 count 初始值为 0,写一个 for 循环将 count 累加到 100,然后尝试在循环外打印 count,观察是否能够访问。
- const 陷阱实验:声明 const person = { name: '小明', age: 18 },然后修改 person.age = 19,再尝试 person = { name: '小红' }。观察哪个操作报错,用自己的话解释 const 的真正含义。
- 块级作用域实践:分别用 var 和 let 在 if(true){ ... } 代码块中声明变量,在块外分别尝试打印,对比两者的差异并记录结论。
- 循环闭包修复:先用 var 写一个 for 循环,在 setTimeout 回调中打印循环变量 i;再将 var 换成 let,观察并解释输出差异。
- 综合练习:写一个函数 getGreeting(user),内部用 let 声明 greeting 变量,根据 user.role 用 if 分支赋值不同的问候语,最后用 const 声明一个配置对象 {time: Date.now()},返回拼接后的完整问候语。