在 JavaScript 中,var、let、const 三种变量声明方式的行为差异是新手常踩的 "坑",尤其是报错场景往往具有迷惑性。本文将从三者的特性出发,结合具体案例解析核心差异,并专门整理常见报错类型及原因,帮助你彻底理清它们的用法。
⭐一、var:历史遗留的变量声明方式
var 是 JavaScript 早期唯一的变量声明方式,因设计缺陷在现代开发中已不推荐使用,其核心特性如下:
🌼1. 函数级作用域,无块级作用域
var 声明的变量仅受函数边界限制,不受 {}、if、for 等块级作用域约束,容易导致变量 "溢出"。
javascript
// 4.js 案例
{
var age = 18; // 块级作用域无法限制 var
let height = 188; // let 受块级作用域限制
}
console.log(age); // 18(正常输出,var 溢出到外部)
console.log(height); // 报错:height is not defined(块外不可访问)
🌼2. 变量提升:声明提前,赋值滞后
var 会将声明提升到作用域顶部,但赋值仍在原位置执行,导致变量在声明前可被访问(值为 undefined)。
javascript
// 2.js 案例
console.log(age); // undefined(声明被提升,赋值未执行)
var age = 18;
// 等价于:
// var age; (提升到顶部)
// console.log(age);
// age = 18;
🌼3. 允许重复声明和重新赋值
同一作用域内可重复声明同一变量,后声明的会覆盖前声明的,易引发意外修改。
javascript
var name = "张三";
var name = "李四"; // 无报错,覆盖前值
console.log(name); // 李四
⭐二、let:ES6 新增的块级作用域变量
let 是 ES6 为解决 var 缺陷引入的声明方式,专为 "可修改的变量" 设计,核心特性如下:
🌼1. 块级作用域:变量仅在当前代码块有效
let 声明的变量被限制在最近的 {} 内,包括循环、条件判断等语句块,有效避免变量污染。
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 输出 0、1、2(每次迭代的 i 独立)
}
console.log(i); // 报错:i is not defined(循环外不可访问)
🌼2. 暂时性死区(TDZ):禁止提前访问
let 不存在变量提升,在声明语句执行前,变量处于 "暂时性死区",无法被访问,否则报错。
javascript
// 2.js 案例
console.log(height); // 报错:Cannot access 'height' before initialization
let height = 188; // 声明后才可用
🌼3. 不允许重复声明,但允许重新赋值
同一作用域内不可重复声明,但可修改变量值。
javascript
let age = 18;
// let age = 20; // 报错:Identifier 'age' has already been declared
age = 20; // 允许重新赋值
console.log(age); // 20
⭐三、const:声明不可重新赋值的常量
const 与 let 特性基本一致,核心区别是声明后不可重新赋值,专为 "常量" 设计:
🌼1. 声明时必须初始化,且不可重新赋值
const 声明必须同时赋值,赋值后不能修改引用(简单类型值不可变,复杂类型引用不可变)。
javascript
// 1.js 案例
const key = "abc123";
key = "abc234"; // 报错:Assignment to constant variable
🌼2. 复杂类型的 "常量":属性可修改,引用不可变
对象 / 数组的内部属性可修改,但不能重新赋值为新的对象 / 数组。
javascript
// 3.js 案例
const person = { name: "ysw", age: 28 };
person.age = 21; // 允许修改属性
console.log(person); // { name: "ysw", age: 21 }
person = { name: "new" }; // 报错:Assignment to constant variable
🌼3. 与 let 相同的作用域和暂时性死区
const 同样支持块级作用域,且存在暂时性死区,行为与 let 一致。
⭐四、常见报错集合及原因解析
在使用 var、let、const 时,以下报错最为常见,需重点掌握:
-
ReferenceError: height is not defined
- 原因:访问了未声明的变量,或变量在作用域之外被调用。
- 案例:
let/const声明的变量在块级作用域外被访问(如 4.js 中console.log(height))。
-
TypeError: Assignment to constant variable
- 原因:对
const声明的变量重新赋值。 - 案例:1.js 中
key = 'abc234',试图修改const变量的值。
- 原因:对
-
ReferenceError: Cannot access 'PI' before initialization
- 原因:在
let/const声明前访问变量,触发暂时性死区(TDZ)。 - 案例:2.js 中
console.log(PI)写在const PI = ...之前。
- 原因:在
-
SyntaxError: Identifier 'age' has already been declared
- 原因:在同一作用域内用
let/const重复声明同一变量(包括与var重复)。 - 案例:
let age = 18; let age = 20;
- 原因:在同一作用域内用
⭐五、var、let、const 总结差异对比表
| 对比维度 | var | let | const |
|---|---|---|---|
| 作用域范围 | 函数级(块级无法约束) | 块级({} 内严格约束) |
块级({} 内严格约束) |
暂时性死区(TDZ) |
❌ 无,声明前访问返回 undefined |
✅ 有,声明前访问抛出 ReferenceError |
✅ 有,声明前访问抛出 ReferenceError |
| 变量提升与 TDZ 关系 | 声明完全提升(无 TDZ 干扰) | 声明提升但被 TDZ 拦截访问 | 声明提升但被 TDZ 拦截访问 |
| 重复声明允许性 | ✅ 允许同作用域重复声明 | ❌ 禁止,重复声明直接报错 | ❌ 禁止,重复声明直接报错 |
| 重新赋值可行性 | ✅ 无限制,可任意重新赋值 | ✅ 允许重新赋值 | ❌ 禁止,赋值后不可改引用 |
| 声明时初始化要求 | ❌ 可选(var x; 合法) |
❌ 可选(let x; 合法) |
✅ 必须(const x; 直接报错) |
| 全局声明与 window 关系 | 会成为 window 属性(污染全局) | 独立存在(不污染 window) | 独立存在(不污染 window) |
| TDZ 典型触发案例 | 无(无 TDZ 机制) | console.log(a); let a = 1; 报错 |
console.log(b); const b = 2; 报错 |
⭐六、最佳实践总结
- 优先使用
const:对于不需要修改的变量(尤其是对象、函数),用const明确语义,减少意外修改。 - 按需使用
let:仅当变量需要重新赋值(如循环计数器、状态变量)时使用let。 - 彻底抛弃
var:避免其函数级作用域和变量提升带来的隐蔽 bug。 - 处理复杂对象 :用
Object.freeze()冻结不需要修改的对象,增强代码安全性。