变量与作用域:var / let / const 到底怎么选?

36 阅读5分钟

写 JS 时用 varlet 还是 const?很多人要么凭感觉,要么“一律用 const”。这篇文章不讲特别玄的底层,只讲三件事:基础概念别混、日常怎么选、坑在哪。适合:已经会写 JS 但概念有点混的、从零开始的小白、以及想打牢基础、校准习惯的前端。

一、先搞清楚:三个关键字分别是什么

1.1 一句话区别

关键字出现时间作用域能否重复声明能否先使用再声明
varES5函数作用域可以可以(会提升)
letES6块级作用域不可以不可以(暂时性死区)
constES6块级作用域不可以不可以(暂时性死区)

用人话说:

  • var:老写法,按“函数”划分地盘,容易踩坑。
  • let:按“块”划分地盘,不能重复声明,更符合直觉。
  • const:和 let 一样是块级,但声明后不能重新赋值(注意:引用类型里的属性可以改)。

1.2 作用域:函数作用域 vs 块级作用域

函数作用域(var): 只认 function,不认 if/for/while 等块。

function fn() {
  if (true) {
    var a = 1;
  }
  console.log(a);  // 1 —— if 块挡不住 var
}

块级作用域(let/const):{},包括 ifforwhile、单独 {}

function fn() {
  if (true) {
    let a = 1;
    const b = 2;
  }
  console.log(a);  // ReferenceError: a is not defined
  console.log(b);  // ReferenceError: b is not defined
}

日常结论: 在块里声明的变量,如果希望“只在这个块里有效”,用 let/const;用 var 会“漏”到整个函数,容易产生隐蔽 bug。

1.3 变量提升(Hoisting)/ˈhɔɪstɪŋ/ 与暂时性死区(TDZ)

ps· TDZ全称:Temporal Dead Zone 音标:/ˈtempərəl/, /ded/ ,/zəʊn/

var:会提升,先使用再声明也不会报错(只是值为 undefined

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

let/const:有暂时性死区,在声明之前访问会报错

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

日常结论: 养成“先声明、再使用”的习惯,用 let/const 可以避免“还没赋值就被用”的坑。

1.4 const 不是“完全不能改”

const 限制的是绑定(不能重新赋值),不限制引用类型内部的修改

const obj = { name: '小明' };
obj.name = '小红';   // ✅ 可以,改的是对象内部
obj = {};            // ❌ 报错,不能换一个对象

const arr = [1, 2, 3];
arr.push(4);         // ✅ 可以
arr = [];            // ❌ 报错

所以:const 适合“这个变量指向的引用不变”的场景,不是“对象/数组内容不能动”。

二、日常写代码:到底怎么选?

2.1 推荐原则(可直接当规范用)

  1. 默认用 const
    只要这个变量不会在逻辑里被重新赋值,就用 const。包括:对象、数组、函数、配置、导入的模块等。

  2. 需要“会变”的变量用 let
    例如:循环计数器、会随逻辑重新赋值的中间变量、交换两数等。

  3. 新代码里不用 var
    除非维护老项目且项目约定用 var,否则一律 let/const

2.2 按场景选

场景推荐原因
导入模块、配置对象、API 地址等const不打算换引用
普通对象、数组(内容会增删改)const引用不变,只改内部
for 循环里的下标 / 循环变量let每次迭代会变
需要先声明、后面再赋值的变量letconst 声明时必须赋初值
交换变量、累加器、临时中间变量let会重新赋值
老项目、历史代码按项目规范,能改则逐步改为 let/const避免混用加重混乱

2.3 简单示例

// ✅ 用 const:引用不变
const API_BASE = 'https://api.example.com';
const user = { name: '张三', age: 25 };
user.age = 26;  // 可以

// ✅ 用 let:会重新赋值
let count = 0;
count++;
let temp;
if (condition) temp = a; else temp = b;

// ❌ 不要用 var(新代码)
var oldStyle = 1;  // 容易漏出块、提升导致误用

三、常见坑:会踩在哪?

3.1 坑一:循环里用 var,回调里拿到的是“最后的那个值”

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(共用一个 i,循环结束后 i 已是 3)

正确写法:let,每次迭代都是新的绑定。

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

3.2 坑二:同一作用域里重复声明 let/const 会报错

let a = 1;
let a = 2;  // SyntaxError: Identifier 'a' has already been declared

var 可以重复声明(不报错),但可读性和维护性差。用 let/const 可以尽早发现“名字写重了”的问题。

3.3 坑三:const 声明时必须赋初值

const x;  // SyntaxError: Missing initializer in const declaration
const y = 1;  // ✅

如果“现在不知道值,后面才赋值”,用 let

3.4 坑四:以为 const 对象/数组“完全不能改”

再次强调:const 限制的是「变量与引用类型的绑定关系」(变量不能指向新的引用地址),而非对象的属性值 / 数组的元素值。我们可以修改的是 “引用类型内部的内容”,比如对象的value、数组的元素。

3.5 坑五:老项目里 varlet/const混用

同一函数里既有 var 又有 let,作用域和提升行为不一致,排查问题会很难。建议:新加的逻辑一律 let/const,老代码有机会就逐步替换成 let/const

四、和“作用域”相关的两个小点

4.1 块级作用域对 if/else 很有用

if (condition) {
  const message = 'yes';
  // 只用在这里
} else {
  const message = 'no';
  // 只用在这里
}
// message 在块外不可见,不污染外部

var 的话,message 会跑到整个函数里,容易重名或误用。

4.2 模块、全局与 window

  • ES Module 里,顶层的 const/let 不会挂到 window 上,和“全局变量”是两回事。
  • 传统脚本里,顶层 var 会变成 window 的属性。
  • 日常:用模块 + const/let,减少全局污染。

五、总结:一张表 + 一句话

要点说明
默认能用 const 就用 const
会重新赋值let
新项目/新代码不用 var
循环 + 异步/回调let,避免 var 的“最后一个值”
const不能重新赋值,但对象/数组内部可以改

一句话: 日常写 JS,默认 const,要改再用 let,别再写 var。先把“选谁”的习惯固定下来,再结合作用域和 TDZ 理解“为什么”,就能少踩坑、代码也更清晰。

以上就是本次的学习分享,欢迎大家在评论区讨论指正,与大家共勉。

我是 Eugene,你的电子学友。

如果文章对你有帮助,别忘了点赞、收藏、加关注,你的认可是我持续输出的最大动力~