var、let、const区别,不要再混淆变量提升
2026/9/16 9:07:27 网站建设 项目流程

说实话,JavaScript 里的变量声明,看起来简单,但真到了写代码的时候,尤其是涉及到varletconst的选择,不少工作了一两年的朋友可能还在凭感觉用。我刚开始学的时候,总觉得“能跑就行”,直到有一次因为var的变量提升,在循环里出了个很难排查的Bug,才痛下决心要把这哥仨彻底搞清楚。

今天咱们不背概念,直接从底层机制和实际场景出发,把这层窗户纸捅破。看完这篇,以后面试官再问,你不仅能答出区别,还能顺便讲讲暂时性死区块级作用域,直接拿捏。

1. 变量提升(Hoisting):var 的“历史遗留问题”

先来看一段代码,你觉得会输出什么?

console.log(a); // undefined var a = 10;

在很多语言里,这肯定直接报错“未定义”。但在 JavaScript (ES5)里,它居然输出undefined。这就是变量提升在作祟。

JavaScript 引擎在编译阶段,会把var声明的变量和function声明挪到当前作用域的顶部。所以上面的代码在引擎眼里,其实是这样的:

var a; // 声明被提升,初始值为 undefined console.log(a); // undefined a = 10; // 赋值留在原地

var就这么个毛病:声明提升了,但赋值没动。如果你在赋值前访问它,拿到的就是undefined,而不是报错。这很容易导致一些隐蔽的 bug,比如变量名拼错了,结果拿到的却是undefined,程序还继续往下跑,最后在某个角落炸掉。

2. 块级作用域:let 和 const 带来的“新世界”

letconst是 ES6 引入的,它们解决了var最大的痛点——没有块级作用域

以前用var,只有全局作用域和函数作用域。比如:

if (true) { var b = 20; } console.log(b); // 20,变量污染了外部

这在for循环里特别坑:

for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // 输出 3, 3, 3

因为i是全局的,循环结束后i变成 3,三个定时器都打印 3。

换成let试试:

for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // 输出 0, 1, 2

let块级作用域,每次循环都会生成一个新的块,把i“锁”在里面。所以定时器拿到的每个i都是独立的副本。

const也一样,声明的变量只在当前大括号{}内有效。

3. 暂时性死区(TDZ):let 和 const 的“严格模式”

你可能听过“let没有变量提升”这个说法。其实严格来说,letconst也有提升,但引擎对它们有一个限制:在声明之前,你不能访问它们

console.log(c); // ReferenceError: Cannot access 'c' before initialization let c = 30;

从进入当前作用域开始,到变量声明完成之前,这块区域就叫暂时性死区(Temporal Dead Zone, TDZ)

let x = 10; if (true) { // 这里就进入了 TDZ console.log(x); // ReferenceError let x = 20; // 声明在此 }

哪怕外面有个同名的x,在块里面声明之前,这个x也是访问不到的。这其实是个好事,强制我们把变量声明放在使用之前,代码更健壮。

4. const 的“不变”陷阱

很多人以为const定义的是常量,不能修改。这句话对了一半。

const PI = 3.14; PI = 3.1415; // TypeError: Assignment to constant variable.

基本类型(数字、字符串、布尔)确实不能改。但const真正保护的是变量的引用地址,而不是值本身。

const person = { name: '张三' }; person.name = '李四'; // 允许!因为引用没变 person = {}; // 报错!尝试改变引用

所以,如果你用const声明一个对象,你是可以修改对象内部属性的,但不能把整个对象重新赋值。

5. 实际开发中到底怎么选?

这个问题我问过很多同事,总结下来有一套比较实用的“心法”:

  • 默认用const:除非你明确知道这个变量后面需要重新赋值,否则一律const。这能防止意外篡改,也让代码的意图更清晰。

  • 需要重新赋值时用let:比如循环计数器、累加变量、或者后续逻辑会改变的变量。

  • 尽量别用var:在现在的工程化项目里,var基本可以退休了。除非你要兼容远古浏览器(比如 IE11),否则没必要再用它。

我自己的习惯是,写 React 组件时,导入的模块用const,状态用let,永远不用var

6. 一张图帮你理清全局对象

还有一个冷知识:在全局作用域下,var声明的变量会成为window(浏览器)或global(Node)的属性,而letconst不会。

var a = 1; let b = 2; console.log(window.a); // 1 console.log(window.b); // undefined

这其实也解释了为什么var容易造成全局污染。如果你正在写一个工具库,一不小心用var声明了个公共变量,很可能就把别人的同名变量覆盖了。

总结一下
特性varletconst
作用域全局/函数作用域块级作用域块级作用域
变量提升是(初始undefined是(但存在 TDZ,不初始化)是(但存在 TDZ,不初始化)
重复声明允许不允许不允许
修改值允许允许基本类型不可改,引用类型可改属性
挂载到window

其实这背后的核心逻辑,就是 JavaScript 在设计上越来越严谨安全var的宽松设计在早期确实方便,但带来的维护成本太高。letconst的引入,本质上是在帮你养成好习惯。

下次面试官再问这个问题,你可以先抛出变量提升和 TDZ 的区别,再结合for循环的经典例子说明块级作用域的好处,最后提一嘴const的引用赋值。这一套组合拳下来,相信对方心里已经有数了。

好了,关于这三者的区别就聊到这儿。如果你在项目中因为变量声明踩过什么坑,也欢迎在评论区分享出来,大家一起避避雷。觉得有用的话,点个赞再走~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询