var、let和const有什么区别?JavaScript变量声明语法对比

作者:函数茶馆

时间:2026-11-04

浏览:253

深入对比JavaScript中var、let和const在作用域、变量提升、暂时性死区、重复声明与赋值限制及全局对象绑定上的核心区别,并提供具体的编码选择建议。

文章目录
在现代 JavaScript 开发中,var、let 和 const 构成了三种不同的标识符声明方式。核心差异在于:作用域边界(var 为函数作用域,let/const 为块级作用域)、变量提升与初始化时机(var 提升并预赋 undefined,let/const 虽提升但存在严格的暂时性死区)、重复声明与可变性限制(var 允许重复声明,const 强制绑定不可重新赋值),以及全局对象属性挂载。日常编写时,应当默认使用 const,仅在值确需重新赋值时使用 let,并完全避免使用遗留的 var。
var、let和const有什么区别?JavaScript变量声明语法对比
图 1:var、let 与 const 基础定位与核心特性速览

一、作用域机制:函数作用域与块级作用域

作用域定义了变量在何处可被访问。在 ECMAScript 2015 (ES6) 之前,JavaScript 只有全局作用域和函数作用域,var 便遵循这一规则;而 let 和 const 则引入了被现代编程语言广泛支持的块级作用域。

1. var 的函数作用域与代码块穿透

var 声明的变量只会被其直接外层的 function 限制。如果 var 声明在 if 分支、for 循环或普通花括号 { } 中,该变量将无视代码块边界,直接穿透到包含它的外部函数或全局作用域中:

function testVarScope() {
  if (true) {
    var count = 10; // 虽然写在 if 块内,但属于整个 testVarScope 函数
  }
  console.log(count); // 示例输出: 10(变量穿透块边界泄露出来)
}
testVarScope();

2. let 与 const 的块级作用域隔离

与 var 截然不同,let 和 const 将任意成对花括号 { } 均视为独立的作用域边界。在块外部无法访问内部声明的标识符,这杜绝了变量在局部控制结构中的意外污染:

function testBlockScope() {
  if (true) {
    let blockVal = 20;
    const fixedVal = 30;
  }
  // 外部访问将直接触发引用错误
  console.log(blockVal); // 抛出错误: ReferenceError: blockVal is not defined
}
testBlockScope();
var、let和const有什么区别?JavaScript变量声明语法对比
图 2:var 穿透代码块与 let/const 块级封闭隔离对照

二、变量提升与暂时性死区(TDZ)

变量在代码中书写的位置与运行时引擎实际登记初始化的时机,是理解 JavaScript 执行机制的关键节点。

1. var 的提升(Hoisting)与静默初始化

在解析执行脚本时,JavaScript 引擎会在进入当前执行上下文时扫描所有 var 声明,将其“提升”至当前函数或全局作用域的最顶端,并默认赋予初始值 undefined。这意味着在 var 语句真正执行前读取该变量,不仅不会报错,反而会静默得到 undefined:

console.log(title); // 示例输出: undefined(变量声明已提升并初始化)
var title = "JavaScript 指南";
console.log(title); // 示例输出: JavaScript 指南

2. let 与 const 的暂时性死区(Temporal Dead Zone)

许多初学者误以为 let 和 const 没有变量提升。规范上,let 和 const 同样会被解析器提前识别并创建在环境记录中,但它们不会在声明语句执行前完成初始化。从包含该声明的代码块开始执行,直到声明语句被正式求值运行完毕的这段区间,被称为暂时性死区(TDZ)。

在 TDZ 期间,对该变量的任何读写操作都会立即抛出 ReferenceError,从而强迫开发者遵循“先声明后使用”的稳健编码习惯:

function tdzDemo() {
  // --- count 的暂时性死区开始 ---
  // console.log(count); // 报错: ReferenceError: Cannot access 'count' before initialization
  
  let count = 42; // --- 暂时性死区结束,完成初始化 ---
  console.log(count); // 示例输出: 42
}
tdzDemo();
var、let和const有什么区别?JavaScript变量声明语法对比
图 3:变量提升机制与暂时性死区(TDZ)执行步骤流程

三、重复声明与赋值可变性对比

在大型工程或团队协作中,无意的重复命名和变量覆盖往往是隐蔽 Bug 的根源。语法层面对重复声明与重新赋值的限制构成了语言健壮性的基石。

1. 重复声明规则

  • var:允许在同一作用域内使用 var 重复声明同名变量。后续声明会被引擎视作普通赋值,静默覆盖早期变量,极易引发难以排查的逻辑覆盖。
  • let 与 const:同一作用域内绝对禁止重复声明相同名称的标识符(包括不能与已有的 var、函数参数或导入同名),若出现则在编译解析阶段立即抛出语法错误 SyntaxError。
// var 的重复声明(静默通过)
var app = "A";
var app = "B"; // 正常运行,静默覆盖

// let / const 的重复声明(语法报错)
let user = "张三";
let user = "李四"; // 报错: SyntaxError: Identifier 'user' has already been declared

2. 重新赋值与 const 的引用本质

let 声明的是可变绑定,允许随时赋予新的值;而 const 声明的是常量绑定,必须满足两条铁律:

  1. 声明时必须赋初值:不能单独书写 const total;,否则抛出语法错误。
  2. 标识符绑定只读:禁止再次对该标识符进行重新赋值(如 total = 100 会抛出 TypeError: Assignment to constant variable)。
特别提醒:const 冻结的是“内存地址绑定”,而非对象本身
使用 const 声明对象或数组时,它仅保证变量名牢牢指向同一个堆内存引用地址。对象内部的属性、数组的成员依然是完全可变的:
const config = { port: 8080 };
config.port = 3000;      // 允许操作:修改对象内部属性有效
// config = { port: 3000 }; // 禁止操作:重新给标识符赋值抛出 TypeError

// 若需彻底禁止修改对象属性,需使用 Object.freeze()
const immutableConfig = Object.freeze({ port: 8080 });
immutableConfig.port = 3000; // 严格模式下抛出错误,普通模式下静默忽略

四、全局对象属性绑定差异

在浏览器宿主环境(window)或 Node.js 环境(globalThis)的顶级作用域中执行代码时,var 与 let/const 的底册挂载方式不同:

  • var 挂载全局对象:在全局作用域下用 var x = 1 声明的变量,不仅存在于作用域链中,还会自动变成全局对象 window 的属性(即可以通过 window.x 访问),极易无意覆盖内置的原生 API(如 window.name、window.location)。
  • let 和 const 不污染全局对象:在全局声明时,它们存储在全局词法环境记录项(Declarative Environment Record)中,独立存在于顶级作用域,不会作为属性挂载到 window 上。
// 全局环境测试
var globalVar = "A";
let globalLet = "B";

console.log(window.globalVar); // 示例输出: "A"(被挂载为 window 属性)
console.log(window.globalLet); // 示例输出: undefined(不污染全局对象)

五、现代 JavaScript 变量声明选择与决策路径

在编写清晰、少 Bug 的现代代码时,建议遵循严格的声明决策树,不再使用 var。

var、let和const有什么区别?JavaScript变量声明语法对比
图 4:现代 JavaScript 标识符声明关键字决策逻辑流程

六、全维度对比总结矩阵

综合以上各技术维度的分析,下表系统梳理了 var、let 与 const 的全部核心特性对照:

var、let和const有什么区别?JavaScript变量声明语法对比
图 5:var、let 与 const 全维度核心特性矩阵对比表

七、总结

理解 var、let 和 const 的设计初衷与底层机制,是编写健壮、可维护 JavaScript 程序的必备功底:

  • var 作为历史遗留语法,存在作用域泄露、提升时静默初始化以及全局污染等缺陷,在现代工程中已不再推荐使用;
  • let 提供了严谨的块级作用域约束和暂时性死区保护,专门用于循环迭代器和状态重赋值场景;
  • const 在块级作用域的基础上确保了标识符引用的不变性,能够显著提升代码的可读性与推导安全性,是日常编码中的默认第一选择。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章