告别 var,拥抱 let/const:一文彻底搞懂 JavaScript 变量与作用域
在现代 JavaScript 开发中,var 已经被视为“历史遗留问题”,绝大多数团队和规范都强烈推荐使用 let 和 const。
但很多人只是“知道要用 let/const”,却并不真正理解它们与 var 的本质区别,以及背后“作用域”和“变量提升”的真实机制。
这篇文章将从最基础的概念到最容易踩坑的细节,一次性帮你彻底搞懂。
一、三个关键字的核心对比表
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域(function scope) | 块级作用域(block scope) | 块级作用域(block scope) |
| 变量提升 | 声明提升 + 初始化为 undefined | 声明提升,但有“暂时性死区”(TDZ) | 声明提升,但有暂时性死区(TDZ) |
| 重复声明 | 允许重复声明 | 不允许在同一作用域重复声明 | 不允许在同一作用域重复声明 |
| 重新赋值 | 允许 | 允许 | 不允许(但对象/数组内容可变) |
| 全局对象绑定 | 是(挂载到 window/global) | 否 | 否 |
| 推荐使用场景 | 基本不推荐 | 需要重新赋值的变量 | 常量、引用不变的对象/数组 |
| ES6 引入 | ES1(1997) | ES6(2015) | ES6(2015) |
二、核心概念拆解
1. 作用域的本质区别
// var → 函数作用域
function test() {
if (true) {
var a = 100;
}
console.log(a); // 100
}
test();
// let/const → 块级作用域(if、for、while、{} 都算块)
function test2() {
if (true) {
let b = 200;
const c = 300;
}
console.log(b); // ReferenceError: b is not defined
}
test2();
经典 for 循环陷阱(面试必考)
// var 版:全部输出 3
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// let 版:正确输出 0 1 2
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
原因:let 在每次循环都会创建一个新的块级作用域变量,而 var 只有一个函数作用域的 i。
2. 变量提升与暂时性死区(TDZ)
console.log(a); // undefined
var a = 1;
// 等价于
var a;
console.log(a);
a = 1;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
const c = 3;
console.log(c); // 正常
c = 4; // TypeError: Assignment to constant variable
暂时性死区(Temporal Dead Zone):let 和 const 的声明会被提升到块顶部,但在声明语句执行之前,访问它们会抛出 ReferenceError。这就是“暂时性死区”。
3. const 的真正含义
const 限制的是“变量的引用”不可变,而不是值不可变
const obj = { name: "小明" };
obj.age = 18; // 允许
obj = { name: "小红" }; // TypeError
const arr = [1, 2, 3];
arr.push(4); // 允许
arr = [5, 6]; // TypeError
最佳实践:
- 需要重新赋值的变量 → 用
let - 不会重新赋值的变量 → 一律用
const(包括对象、数组)
三、常见面试/真实开发中的坑
- var 在 for 循环中的经典错误(已上面展示)
- 立即执行函数解决 var 问题(老写法)
for (var i = 0; i < 3; i++) {
(function (i) {
setTimeout(() => console.log(i), 0);
})(i);
}
- let 在 for 循环中的神奇行为
let funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2
- const 定义的对象可以修改内容,但不能重新指向
- 全局 var 与 let 的区别
var x = 1; // window.x = 1
let y = 2; // window.y 不存在
console.log(window.x); // 1
console.log(window.y); // undefined
四、现代 JavaScript 变量声明推荐原则(2024–2025)
- 默认使用 const
- 当需要重新赋值时才改用 let
- 永远不要再用 var(除非维护 10 年前的遗留代码)
- 在 for…of、for…in、forEach 等循环中优先使用 const(迭代变量通常不需要重新赋值)
// 推荐写法
const users = await fetchUsers();
for (const user of users) {
console.log(user.name);
}
const config = { timeout: 5000 };
config.retry = 3; // 允许
五、总结一句话口诀
- var:函数作用域 + 提升 + 可重复声明 → 容易出 Bug → 淘汰
- let:块级作用域 + 有 TDZ + 可重新赋值 → 替代 var 的主要工具
- const:块级作用域 + 有 TDZ + 引用不可变 → 现代默认选择
记住这一句:
“能用 const 就用 const,需要改值才用 let,var 已经可以去博物馆了。”
如果你还有下面这些具体疑问,欢迎继续问:
- let/const 在 for 循环的底层实现到底是什么?
- 暂时性死区(TDZ)在编译阶段是怎么处理的?
- const 对象/数组到底能改到什么程度?
- 实际项目中如何迁移 var 到 let/const?
一起把作用域和变量彻底搞清楚!