JavaScript 变量声明终极指南:彻底搞懂 var、let、const
变量声明是 JavaScript 语言演进中最重要的升级点之一。从最初只能使用 var,到 ES6 引入 let 与 const,JS 的作用域机制、可维护性与可预测性都发生了巨大的变化。本文将从原理层面梳理三者的差异,并结合常见错误与最佳实践,帮助你构建现代 JS 的变量使用思维模型。
一、变量声明的时代分水岭
1. ES5 时代:只有 var
早期 JS 使用 var 声明变量:
var age = 18;
var PI = 3.1415926;
其特点是:
- 函数级作用域,不支持块级作用域
- 变量提升:声明会被提升到当前作用域顶部
- 可重复声明,容易污染作用域
这导致大型项目中难以维护,语义也不清晰。
2. ES6 引入 let 与 const
为了修复 var 的缺陷,ES6 带来了现代声明方式:
let:可变变量const:常量绑定
let count = 10;
const TOKEN = "abc123";
它们不仅提供了更清晰的语义,还引入了「块级作用域」与「暂时性死区」。
二、编译阶段 vs 执行阶段:理解变量异常的关键
JavaScript 代码执行分两步:
- 编译阶段:扫描、注册变量与函数
- 执行阶段:按顺序执行逻辑、赋值
var、let、const 的行为差异全部源于这两个阶段。
三、var 的行为模型
1. 变量提升(Hoisting)
console.log(a); // undefined
var a = 1;
编译等价于:
var a;
console.log(a);
a = 1;
声明被提升,赋值不会被提升。
这是导致很多初学者困惑的根源。
2. 函数级作用域
{
var x = 100;
}
console.log(x); // 100
块级作用域({})完全无法隔离变量。
四、let 与 const:现代 JS 的根基
1. 块级作用域(Block Scope)
{
let h = 180;
const PI = 3.14;
}
console.log(h); // ReferenceError
作用域更安全、更可控。
2. 暂时性死区(TDZ)
console.log(n); // ReferenceError
let n = 10;
变量在声明前不可访问,直接杜绝了变量提升导致的隐性 bug。
3. const 的特殊性
const obj = { a: 1 };
obj.a = 2; // 允许修改内部属性
obj = {}; // 不允许重新赋值
如果你需要真正的只读对象:
Object.freeze(obj);
五、常见错误拆解
| 错误 | 原因 |
|---|---|
| ReferenceError: 未定义变量 | 跨作用域访问 |
| ReferenceError: before initialization | 触发 TDZ |
| TypeError: Assignment to constant variable | 修改 const 绑定 |
调试建议:
- 永远不要在声明前访问变量
- 尽量减少变量作用域跨度
- 只在确实需要可变时使用
let
六、函数提升与表达式差异
fn(); // 正常
function fn() {}
bar(); // ReferenceError
let bar = function(){};
- 函数声明:整体提升
- 函数表达式:受制于变量声明方式,会触发 TDZ
七、实用最佳实践
| 场景 | 建议 |
|---|---|
| 默认情况 | 使用 const |
| 需要修改变量时 | 使用 let |
| 避免使用 | var |
| 对象需要不可变 | Object.freeze() |
| 不要把“声明”和“使用”分得太开 | 避免 TDZ |
八、现代 JS 的变量哲学
var 是历史遗留物,容易导致隐性 bug。
let/const 提供了更可靠、更可预测的编程体验。
牢记三条黄金法则:
- 默认用
const - 确实需要改变值时用
let - 永远不要提前访问变量
掌握这套模型,你的 JS 代码质量将提升一个量级。