现代 JavaScript 变量声明的终极指南:var、let、const 的核心区别与实战应用

12
发布时间:2026-03-20 19:37:34

在 JavaScript 的演进史上,2015 年发布的 ES6(ECMAScript 2015)无疑是一个里程碑。它不仅引入了类、模块和箭头函数,更彻底重塑了我们声明变量的方式——let 和 const 的诞生

直到今天,许多开发者依然在面对 varletconst 时感到困惑:到底该用哪个?为什么官方推荐不再使用 var

本文将带你深入理解这三者的核心差异,通过实战场景剖析,帮你建立一套现代化的变量声明思维模型。


一、为什么我们需要改变?var 的“原罪”

在 ES6 之前,var 是我们声明变量的唯一选择。但它有两个著名的“设计缺陷”,常常导致难以排查的 Bug:

1. 函数作用域带来的“泄露”

var 只有函数作用域,没有块级作用域。这意味着在 iffor 等代码块中声明的变量,会“泄露”到整个函数甚至全局作用域中。

function getData() {
  if (true) {
    var data = "敏感数据";
  }
  // data 在这里依然可以访问!这往往不是我们想要的
  console.log(data); 
}

2. 变量提升(Hoisting)的陷阱

var 声明的变量会被提升到作用域顶部,但初始化不会。这导致你在声明之前访问变量,得到的不是报错,而是神秘的 undefined

console.log(user); // 输出:undefined (而不是报错)
var user = "Alice";

这种“静默失败”是无数逻辑错误的温床。


二、新三巨头的核心对决

为了解决上述问题,let 和 const 应运而生。它们不仅修复了作用域问题,还引入了暂时性死区(TDZ),让错误尽早暴露。

📊 一张表看懂核心区别

表格

特性 var(旧时代) let(现代可变) const(现代常量)
作用域 函数级 块级 ({}) 块级 ({})
变量提升 ✅ 有 (初始化为 undefined) ❌ 无 (存在暂时性死区) ❌ 无 (存在暂时性死区)
重复声明 ✅ 允许 ❌ 禁止 ❌ 禁止
重新赋值 ✅ 允许 ✅ 允许 ❌ 禁止
必须初始化
全局对象属性 是 (挂载到 window)

💡 关键概念:暂时性死区 (TDZ)
在使用 let 或 const 声明变量之前,任何对该变量的访问都会直接抛出 ReferenceError。这强制你“先声明,后使用”,从根源上杜绝了隐式错误。


三、深度解析:那些你容易踩的坑

1. 循环中的经典陷阱

这是面试中最常考的场景,也是 var 最致命的弱点。

❌ 错误示范 (var):

for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i); 
  }, 1000);
}
// 输出:5, 5, 5, 5, 5
// 原因:i 是函数作用域,所有定时器共享同一个 i,循环结束时 i 变成了 5

✅ 正确示范 (let):

for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i); 
  }, 1000);
}
// 输出:0, 1, 2, 3, 4
// 原因:let 拥有块级作用域,每次循环都会创建一个新的 i 绑定

2. const 真的是“常量”吗?

很多新手误以为 const 定义的内容完全不可变,这是一个巨大的误区。

const 保证的是“引用地址”不变,而不是“内容”不变。

const user = { name: "Alice", age: 25 };

// ✅ 允许:修改对象内部的属性
user.age = 26; 
user.name = "Bob";

// ❌ 禁止:重新赋值给新的对象
user = { name: "Charlie" }; // TypeError: Assignment to constant variable.

🚀 进阶技巧:如何真正冻结对象?
如果你希望对象连属性都不能修改,需要配合 Object.freeze()

const config = Object.freeze({
  apiVersion: "v1",
  debug: false
});

config.debug = true; // 严格模式下报错,非严格模式下静默失败

四、实战场景:如何优雅地选择?

在现代开发中,我们不再纠结“能不能用”,而是遵循一套最佳实践优先级

🏆 黄金法则:默认 const,按需 let,拒绝 var

场景 1:配置项与工具函数

首选 const。配置、API 地址、数学常数等一旦定义就不应改变的数据。

const API_BASE_URL = "https://api.example.com";
const MAX_RETRY_COUNT = 3;
const formatDate = (date) => { ... };

场景 2:状态管理与循环计数器

使用 let。当你知道这个变量在未来需要被重新赋值时(如累加器、开关状态、循环索引)。

let isLoading = false;
let currentIndex = 0;

for (let i = 0; i < items.length; i++) {
  // 处理逻辑
}

场景 3:维护老旧代码

仅在必要时保留 var。如果你在维护一个必须兼容 IE9 以下浏览器的古老项目,或者在处理一些依赖 var 提升特性的遗留逻辑(极少见),才考虑使用 var。否则,请果断重构。


五、避坑指南与代码规范

⚠️ 警惕隐式全局变量

忘记写声明关键字是新手常犯的错误。在非严格模式下,直接赋值会创建全局变量,污染 window 对象。

// ❌ 危险
function test() {
  count = 10; // 隐式创建了全局变量 window.count
}

// ✅ 安全
function test() {
  let count = 10; // 局部变量
}

🛠️ 利用 ESLint 自动化规范

不要依赖人工检查,让工具帮你守住底线。在 .eslintrc 中配置:

{
  "rules": {
    "no-var": "error",          // 禁止使用 var
    "prefer-const": "error",    // 如果变量未被重新赋值,强制使用 const
    "no-unused-vars": "warn"    // 警告未使用的变量
  }
}

开启 prefer-const 规则后,如果你写了 let x = 1 但后续没改过 x,ESLint 会提示你改为 const,这能极大提升代码的可读性和安全性。


六、结语:拥抱更安全的 JavaScript

从 var 到 let/const 的转变,不仅仅是语法的更新,更是编程思维的升级

  1. 更小的作用域:减少变量污染,让逻辑更清晰。
  2. 更早的报错:暂时性死区让错误在开发阶段就暴露,而不是在生产环境爆发。
  3. 更强的意图表达:使用 const 告诉阅读代码的人,“这个数据不应该被改变”,降低了心智负担。

记住这句口诀:

默认全用 const,非要改动才 let,除非万不得已,否则别碰 var

当你开始习惯这种写法,你会发现你的代码变得更加健壮、易读,且充满了现代工程化的美感。

本文被 前端开发 专题收录