JavaScript 变量声明详解:var、let 与 const 的演进与实践

1 阅读5分钟

JavaScript 变量声明详解:varletconst 的演进与实践

摘要:本文系统梳理 JavaScript 中三种变量声明方式(varletconst)的核心机制,深入剖析 ES5 与 ES6 在作用域、变量提升等方面的本质差异,并结合典型错误场景,提供现代 JavaScript 开发的最佳实践建议。全文约 2000 字,适合前端开发者巩固基础。


一、引言:从混乱到规范的变量声明

在早期 JavaScript(ES5 及之前),开发者只能使用 var 声明变量。然而,var 的行为常常“反直觉”——变量似乎可以在声明前被访问、作用域边界模糊、重复声明不会报错……这些问题在大型项目中极易引发难以追踪的 bug。

2015 年,ECMAScript 6(ES6/ES2015)正式发布,引入了 letconst,从根本上解决了 var 的缺陷,使 JavaScript 的变量管理更加严谨、可预测。理解这三者的区别,是掌握现代 JavaScript 的基石。


二、var 的工作机制与问题

1. 函数作用域(Function Scope)

var 声明的变量仅在函数内部有效,而非代码块(如 iffor)内:

function test() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10 —— 在 if 外仍可访问!
}

这种设计导致变量“泄漏”出预期的作用域,违背模块化编程原则。

2. 变量提升(Hoisting)

JavaScript 引擎在执行代码前会经历编译阶段(用于语法检查和变量/函数声明收集)。var 声明会被“提升”到其作用域顶部,但赋值留在原地

console.log(a); // undefined(不是报错!)
var a = 1;

等价于:

var a;          // 编译阶段:声明被提升
console.log(a); // 执行阶段:a 为 undefined
a = 1;          // 赋值未提升

这种“可访问但值为 undefined”的行为极易造成逻辑错误,且难以调试。

3. 允许重复声明

var name = "Alice";
var name = "Bob"; // 不报错!name 变为 "Bob"

这种宽松性在协作开发中可能掩盖命名冲突。


三、ES6 的革新:letconst

为解决 var 的问题,ES6 引入了块级作用域(Block Scope)和两个新关键字:

1. 块级作用域({} 内有效)

{
  let y = 20;
  const z = 30;
}
console.log(y); // ReferenceError: y is not defined
console.log(z); // ReferenceError: z is not defined

现在,iffor{} 等任何代码块都能形成独立作用域,变量生命周期更清晰。

2. 暂时性死区(Temporal Dead Zone, TDZ)

虽然 let/const 也会被提升,但在声明前访问会抛出错误

console.log(PI); // ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14;

从进入作用域到变量声明之间的区域称为 TDZ。这强制开发者“先声明后使用”,避免 var 的陷阱。

3. 禁止重复声明

let count = 0;
let count = 1; // SyntaxError: Identifier 'count' has already been declared

这有助于提前发现命名冲突。

4. const 的“常量”语义

  • 不可重新赋值
    const MAX = 100;
    MAX = 200; // TypeError: Assignment to constant variable.
    
  • 但对象/数组内容可变
    const user = { name: "Tom" };
    user.name = "Jerry"; // ✅ 合法
    user = {};           // ❌ 非法
    

const 实际上是创建一个不可变的绑定,而非不可变的值。


四、关键概念对比表

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升是(初始化为 undefined是(TDZ)是(TDZ)
重复声明允许禁止禁止
重新赋值允许允许禁止(绑定不可变)
全局属性是(如 window.a

五、典型错误场景解析

  1. ReferenceError: height is not defined
    → 在作用域外访问 let/const 变量。

    if (true) { let height = 180; }
    console.log(height); // 报错
    
  2. TypeError: Assignment to constant variable.
    → 尝试给 const 重新赋值。

    const API_URL = "https://api.example.com";
    API_URL = "https://new-api.com"; // 报错
    
  3. ReferenceError: Cannot access 'PI' before initialization
    → 在 TDZ 中访问变量。

    console.log(PI);
    const PI = 3.14; // 报错
    

六、函数提升 vs 变量提升

除了变量,函数声明也会被提升,且整个函数体被提升

sayHello(); // "Hello!" —— 函数可直接调用
function sayHello() {
  console.log("Hello!");
}

函数表达式(用 var/let 声明)遵循变量提升规则:

// var 形式
console.log(typeof greet); // "function"
greet(); // "Hi"
var greet = function() { console.log("Hi"); };

// let 形式
console.log(typeof hello); // ReferenceError(TDZ)
let hello = function() { console.log("Hi"); };

七、const常量可变性

  • const常量声明的简单数据类型的变量不会可以改变
  • const常量声明的复杂数据类型比如字面量对象可以改变
  const person={
 age:45;
  }
  age=56;

注意:使用冻结操作可以使得复杂数据类型也不可以被改变

const PI = 3.1415926;
const person={
    name:"ysw",
    age:25,


}
// person = 'hahahaha';
person .age = 21;
console.log(person);
// 简单数据类型的常量不可以改变;
// 复杂数据类型的常量,不能改变引用的地址,但是可以改变引用地址中的属性值
// 如果对象一定不可以变呢?
const wes = Object.freeze(person );//冻结对象
console.log(wes);
wes.age=456;
console.log(wes);


八、最佳实践建议

  1. 默认使用 const
    除非明确需要重赋值,否则一律用 const。这能防止意外修改,提高代码健壮性。

  2. 需要重赋值时用 let
    如循环计数器、状态标志等。

  3. 彻底避免 var
    现代项目(尤其是使用 ESLint、TypeScript)应禁用 var

  4. 理解 TDZ 和块级作用域
    这是 LeetCode 等算法平台考察 JS 行为的关键点(如 LeetCode 热题 100 中涉及闭包、作用域的题目)。


九、结语

varlet/const,JavaScript 完成了变量管理机制的一次重要进化。这一变化不仅修复了历史遗留问题,更推动了开发者编写更安全、更可维护的代码。

记住口诀

  • const 优先,不变就用它;
  • 需要重赋值,let 来保驾;
  • var 已过时,坚决不使用。

掌握这些原理,你不仅能写出正确的代码,更能深入理解 JavaScript 的执行机制,为学习闭包、异步、模块化等高级主题打下坚实基础。