JavaScript 中的变量声明:var、let 与 const 深度解析

在 JavaScript 的发展历程中,变量声明机制经历了从混乱到规范的重大演进。早期的 JavaScript(ES5 及之前)仅提供 var 关键字来声明变量,但其行为常常与开发者的直觉相悖,尤其是在变量提升(Hoisting)作用域 方面。随着 ES6(ECMAScript 2015)的发布,letconst 被引入,极大地改善了语言的可读性、可维护性和安全性,使 JavaScript 更适合大型企业级项目的开发。

本文将结合代码实例,深入剖析 varletconst 的核心差异,帮助开发者理解其底层机制,并掌握最佳实践。


一、var:被时代淘汰的变量声明方式

1. 变量提升(Hoisting)------"编译阶段"与"执行阶段"的分离

var 最令人困惑的特性是变量提升 。JavaScript 引擎在执行代码前会进行一个"编译"阶段,此时会收集所有 var 声明的变量,并将它们提升到当前作用域的顶部。

ini 复制代码
console.log(age); // 输出:undefined
var age = 18;

这段代码的实际执行过程如下:

javascript 复制代码
// 编译阶段:变量提升
var age; // 声明被提升,但未赋值

// 执行阶段
console.log(age); // 此时 age 存在但值为 undefined
age = 18; // 赋值

这种行为导致了"提前访问但值为 undefined"的问题,容易引发难以调试的 bug。

2. 缺乏块级作用域

var 声明的变量只具有函数作用域全局作用域 ,不支持块级作用域(如 {} 内的作用域)。

ini 复制代码
{
    var age = 18;
    let height = 188;
}

console.log(age);     // 输出:18 ------ var 变量在块外仍可访问
console.log(height);  // 报错:ReferenceError: height is not defined

height 使用 let 声明,受限于块级作用域,无法在块外访问,而 age 使用 var 声明,其作用域被提升至全局,破坏了代码的封装性。

报错:ReferenceError: height is not defined

该报错是因为 试图访问一个在当前作用域中不存在的变量 height,这是因为在块级作用域外访问了 let/const 声明的变量。


二、let:现代 JavaScript 的变量声明标准

let 的引入解决了 var 的两大痛点:变量提升带来的可读性问题缺乏块级作用域

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

let 声明的变量虽然在编译阶段也会被"提升",但不会被初始化,访问未初始化的 let 变量会抛出 ReferenceError

ini 复制代码
console.log(PI); // 报错:ReferenceError: Cannot access 'PI' before initialization
let height = 188;
const PI = 3.1415926;

这被称为暂时性死区------从进入作用域到变量被实际声明之间的区域。在此区域内访问变量是非法的,这迫使开发者遵循"先声明后使用"的编程规范,提高了代码的健壮性。

报错:ReferenceError: Cannot access 'PI' before initialization

该报错是因为 在 constlet 声明之前访问了该变量,处于"暂时性死区"(TDZ),JavaScript 禁止在此阶段访问,以保证安全的初始化顺序。

2. 支持块级作用域

let 支持真正的块级作用域,变量仅在声明它的代码块 {} 内有效。

arduino 复制代码
{
    let height = 188;
}
console.log(height); // 报错:ReferenceError: height is not defined

这一特性使得变量的作用范围更加清晰,避免了命名冲突和意外修改,是现代大型项目开发的核心基础。

报错:ReferenceError: height is not defined

该报错是因为 试图访问一个未声明或在当前作用域中不存在的变量 height,这是因为在块级作用域外访问了 let/const 声明的变量。


三、const:声明不可变的常量

const 用于声明一个常量,其值在声明后不可重新赋值。

1. 基本数据类型的不可变性

对于字符串、数字等基本数据类型,const 确保其值不可更改。

ini 复制代码
const key = 'abc123';
key = 'abc234'; // 报错:TypeError: Assignment to constant variable.

报错:TypeError: Assignment to constant variable.

该报错是因为 尝试给用 const 声明的常量重新赋值,这是不允许的,因为 const 变量一旦初始化就不能再被重新赋值。

2. 引用类型的"引用不可变性"

对于对象、数组等复杂数据类型,const 保证的是引用地址不可变,但可以修改其内部的属性或元素。

ini 复制代码
const person = {
    name: "zjy",
    age: 21
};

person.age = 22; // 合法:修改对象内部属性
console.log(person); // { name: "zjy", age: 22 }

person = {}; // 报错:TypeError: Assignment to constant variable.

报错:TypeError: Assignment to constant variable.

该报错是因为 尝试给用 const 声明的常量重新赋值,这是不允许的,因为 const 变量一旦初始化就不能再被重新赋值。

3. 如何真正冻结对象?

如果希望对象完全不可变,可以使用 Object.freeze()

ini 复制代码
const wes = Object.freeze(person);
wes.age = 17; // 在严格模式下会报错,非严格模式下静默失败
console.log(wes); // 仍为 { name: "zjy", age: 22 }

Object.freeze() 可以深度冻结对象,防止其属性被修改、添加或删除。


四、函数提升:var 与函数声明的对比

JavaScript 中函数是"一等公民",函数声明也会被提升,且连同函数体一起提升

scss 复制代码
setWidth(); // 可以正常调用

function setWidth() {
    var width = 100;
    console.log(width); // 输出:100
}

注意:var 声明的变量只会提升声明(初始化为 undefined),而函数声明会提升整个函数定义,因此可以在声明前调用。

但如果是函数表达式 ,则行为与 var 相同:

scss 复制代码
getAge(); // 报错:TypeError: getAge is not a function

var getAge = function() {
    console.log(18);
};

报错:TypeError: getAge is not a function

调用了一个名为 getAge 的函数,但它实际上是一个未初始化的 var 变量(如函数表达式),由于变量提升导致其值为 undefined,因此无法调用。


五、最佳实践总结

varletconst的主要区别

特性 var let const
作用域 函数/全局 块级 块级
变量提升 是(值为 undefined 是(进入暂时性死区) 是(进入暂时性死区)
重复声明 允许 不允许 不允许
重新赋值 允许 允许 不允许
推荐使用 ❌ 不推荐 ✅ 推荐用于变量 ✅ 推荐用于常量

✅ 推荐做法:

  1. 永远不要再使用 var 。它已被 letconst 取代。
  2. 优先使用 const 。如果变量不会被重新赋值,就用 const
  3. 仅在需要重新赋值时使用 let
  4. 对复杂数据类型使用 Object.freeze() 来确保完全不可变。

为了更好的理解这三者的区别,以下是关于变量提升暂时性死区块级作用域三个核心概念的对比解析

概念 var 表现 let 表现 const 表现
变量提升 (Hoisting) ✅ 声明被提升到作用域顶部,初始化为 undefined (可访问,值为 undefined ✅ 声明被提升,但不初始化 (进入"暂时性死区",访问报错) ✅ 声明被提升,但不初始化 (进入"暂时性死区",访问报错)
暂时性死区 (Temporal Dead Zone, TDZ) ❌ 不存在 (提升后即可访问,值为 undefined ✅ 存在 从进入作用域到变量声明前的区域 访问会抛出 ReferenceError ✅ 存在 规则与 let 相同 在声明前访问会报错
块级作用域 (Block Scope) ❌ 不支持 仅函数/全局作用域 在 {} 内声明的 var 变量可在外部访问 ✅ 支持 变量仅在 {} 块内有效 块外访问会抛出 ReferenceError ✅ 支持 规则与 let 相同 常量作用域限制在声明的代码块内

✅ 概念精要总结:

  • 变量提升 :JS 引擎在"编译阶段"收集变量和函数声明,并提升到作用域顶部。var 提升后初始化为 undefined,而 let/const 提升但不初始化,形成"暂时性死区"。
  • 暂时性死区letconst 变量从进入作用域到被实际声明之间的"禁用区",在此区域内访问变量会抛出 ReferenceError,强制开发者遵循"先声明后使用"的良好习惯。
  • 块级作用域 :由 {} 定义的作用域范围。letconst 支持块级作用域,使变量生命周期更清晰,避免命名污染;var 不支持,容易导致意外的变量泄漏。

掌握这三个概念,就掌握了现代 JavaScript 变量声明机制的底层逻辑。

针对varletconst 运用时会出现的报错对 ReferenceErrorTypeError 两种错误类型进行的结构化解析:

错误类型 var 相关典型表现 let 相关典型表现 const 相关典型表现
ReferenceError ✅ 访问未声明的变量 例如:console.log(age); 但从未声明 var age; 或函数表达式提前调用导致 is not a function ✅ 在声明前访问变量 例如:console.log(height); let height = 188; 触发"暂时性死区"错误 ✅ 在声明前访问常量 例如:console.log(PI); const PI = 3.14; 同样进入暂时性死区,报错
TypeError ✅ 调用未赋值的函数表达式 例如:getAge(); var getAge = function() {...}; 输出:TypeError: getAge is not a function ✅ 尝试重新声明已存在的 let 变量(非赋值) 例如:let a = 1; let a = 2; (严格来说是语法错误,但引擎常报 TypeError 或 SyntaxError) ✅ 修改常量引用 例如:const key = 'abc'; key = 'def'; 输出:TypeError: Assignment to constant variable.

✅ 报错类型精要总结:

  • ReferenceError :表示"找不到这个变量或标识符",通常是因为变量未声明拼写错误 或在 let/const暂时性死区内提前访问
  • TypeError :表示"操作不被允许",通常是因为试图改变 const 的值调用尚未赋值的函数表达式,或对数据类型执行了非法操作。

理解这两类错误的触发场景,有助于快速定位 JavaScript 中的声明与执行逻辑问题,尤其是在涉及变量提升与作用域机制时。


结语

JavaScript 的演进,本质上是对开发者心智模型的不断贴近。var 所带来的变量提升与作用域泄漏,曾让无数开发者陷入困惑;而 letconst 的引入,则是对"直觉编程"的一次回归------先声明,再使用;块内声明,块内有效;常量一旦定义,便不可更改。

这不仅是语法的更新,更是编程思维的升级。体现了 JavaScript 语言从"脚本语言"向"现代编程语言"的成熟。选择 const 而非 let,选择块级作用域而非函数作用域,是在用代码表达意图,是在构建更可靠、更易维护的系统。在现代 JavaScript 开发中,摒弃 var 不仅是一种技术选择,更是一种对代码质量的承诺。

掌握 letconst,意味着我们不再与语言的怪癖对抗,而是借助其设计,写出更清晰、更安全、更具可读性的程序。这才是语言进步的真正意义。

相关推荐
W.Y.B.G3 小时前
css3 学习笔记
笔记·学习·css3·1024程序员节
摇滚侠3 小时前
Spring Boot3零基础教程,函数式 Web 新特性,笔记51
java·spring boot·笔记
moringlightyn4 小时前
c++ 智能指针
开发语言·c++·笔记·c++11·指针·智能指针
Mintopia4 小时前
🌐 数据合规框架下的 WebAIGC 训练数据处理技术规范
前端·javascript·aigc
用户6600676685394 小时前
从 var 到 let/const:JavaScript 变量声明的进化之路
javascript
十年_H4 小时前
Cesium自定义着色器-片元着色器数据来源
javascript·cesium
UIUV4 小时前
var、let 与 const:JavaScript 变量声明的演进与最佳实践
javascript
风已经起了5 小时前
FPGA学习笔记——用Vitis IDE生成工程(串口发送)
笔记·学习·fpga开发·fpga·1024程序员节
阿珊和她的猫5 小时前
深入剖析 Vue Router History 路由刷新页面 404 问题:原因与解决之道
前端·javascript·vue.js