写 JS 时用 var、let 还是 const?很多人要么凭感觉,要么"一律用 const"。这篇文章不讲特别玄的底层,只讲三件事:基础概念别混、日常怎么选、坑在哪。适合:已经会写 JS 但概念有点混的、从零开始的小白、以及想打牢基础、校准习惯的前端。
一、先搞清楚:三个关键字分别是什么
1.1 一句话区别
| 关键字 | 出现时间 | 作用域 | 能否重复声明 | 能否先使用再声明 |
|---|---|---|---|---|
var |
ES5 | 函数作用域 | 可以 | 可以(会提升) |
let |
ES6 | 块级作用域 | 不可以 | 不可以(暂时性死区) |
const |
ES6 | 块级作用域 | 不可以 | 不可以(暂时性死区) |
用人话说:
var:老写法,按"函数"划分地盘,容易踩坑。let:按"块"划分地盘,不能重复声明,更符合直觉。const:和let一样是块级,但声明后不能重新赋值(注意:引用类型里的属性可以改)。
1.2 作用域:函数作用域 vs 块级作用域
函数作用域(var): 只认 function,不认 if/for/while 等块。
javascript
function fn() {
if (true) {
var a = 1;
}
console.log(a); // 1 ------ if 块挡不住 var
}
块级作用域(let/const): 认 {},包括 if、for、while、单独 {}。
javascript
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)
javascript
console.log(x); // undefined
var x = 10;
console.log(x); // 10
let/const:有暂时性死区,在声明之前访问会报错
javascript
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
日常结论: 养成"先声明、再使用"的习惯,用 let/const 可以避免"还没赋值就被用"的坑。
1.4 const 不是"完全不能改"
const 限制的是绑定 (不能重新赋值),不限制引用类型内部的修改。
javascript
const obj = { name: '小明' };
obj.name = '小红'; // ✅ 可以,改的是对象内部
obj = {}; // ❌ 报错,不能换一个对象
const arr = [1, 2, 3];
arr.push(4); // ✅ 可以
arr = []; // ❌ 报错
所以:const 适合"这个变量指向的引用不变"的场景,不是"对象/数组内容不能动"。
二、日常写代码:到底怎么选?
2.1 推荐原则(可直接当规范用)
-
默认用
const只要这个变量不会在逻辑里被重新赋值 ,就用
const。包括:对象、数组、函数、配置、导入的模块等。 -
需要"会变"的变量用
let例如:循环计数器、会随逻辑重新赋值的中间变量、交换两数等。
-
新代码里不用
var除非维护老项目且项目约定用
var,否则一律let/const。
2.2 按场景选
| 场景 | 推荐 | 原因 |
|---|---|---|
| 导入模块、配置对象、API 地址等 | const |
不打算换引用 |
| 普通对象、数组(内容会增删改) | const |
引用不变,只改内部 |
| for 循环里的下标 / 循环变量 | let |
每次迭代会变 |
| 需要先声明、后面再赋值的变量 | let |
const 声明时必须赋初值 |
| 交换变量、累加器、临时中间变量 | let |
会重新赋值 |
| 老项目、历史代码 | 按项目规范,能改则逐步改为 let/const | 避免混用加重混乱 |
2.3 简单示例
javascript
// ✅ 用 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,回调里拿到的是"最后的那个值"
javascript
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(共用一个 i,循环结束后 i 已是 3)
正确写法: 用 let,每次迭代都是新的绑定。
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2
3.2 坑二:同一作用域里重复声明 let/const 会报错
javascript
let a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared
var 可以重复声明(不报错),但可读性和维护性差。用 let/const 可以尽早发现"名字写重了"的问题。
3.3 坑三:const 声明时必须赋初值
javascript
const x; // SyntaxError: Missing initializer in const declaration
const y = 1; // ✅
如果"现在不知道值,后面才赋值",用 let。
3.4 坑四:以为 const 对象/数组"完全不能改"
再次强调:const 限制的是「变量与引用类型的绑定关系」(变量不能指向新的引用地址),而非对象的属性值 / 数组的元素值。我们可以修改的是 "引用类型内部的内容",比如对象的value、数组的元素。
3.5 坑五:老项目里 var 和 let/const混用
同一函数里既有 var 又有 let,作用域和提升行为不一致,排查问题会很难。建议:新加的逻辑一律 let/const,老代码有机会就逐步替换成 let/const。
四、和"作用域"相关的两个小点
4.1 块级作用域对 if/else 很有用
javascript
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,你的电子学友。
如果文章对你有帮助,别忘了点赞、收藏、加关注,你的认可是我持续输出的最大动力~