let、var、const 的区别
七崽爱吃小饼干2026/10/07阅读 0
同一个变量,用三种声明写出来,行为能差到让人怀疑人生:
js
var a = 1
let b = 2
const c = 3
console.log(window.a) // 1,var 挂到了全局对象上
console.log(window.b) // undefined,let 不会
console.log(c) // 3
let、const、var 的区别不只是"能不能改",它牵涉到作用域、变量提升、暂时性死区、全局对象挂载这几件事。这篇文章按这几个维度逐一拆开,最后给一张速查表和选型建议。
一、作用域:函数作用域 vs 块级作用域
var 是函数作用域,只认函数的大括号,不认 if、for 这种块:
js
function fn() {
if (true) {
var x = 1
}
console.log(x) // 1,块外依然能访问
}
fn()
let 和 const 是块级作用域,任何一对 {} 都是一道墙:
js
if (true) {
let x = 1
const y = 2
}
console.log(x) // ReferenceError: x is not defined
console.log(y) // ReferenceError: y is not defined
这个差异在循环里最经典,也是当初引入 let 的核心动机:
js
// var:三个闭包共用同一个 i,最终都输出 3
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)) // 3 3 3
}
// let:每次迭代生成新的绑定,各自记住自己的 i
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i)) // 0 1 2
}
二、变量提升与暂时性死区
三者都有"提升",但表现完全不同。
var 的声明会被提升,并初始化为 undefined:
js
console.log(a) // undefined,不报错
var a = 1
// 等价于
// var a
// console.log(a)
// a = 1
let 和 const 也会提升,但不会初始化,在声明之前访问会进入"暂时性死区"(Temporal Dead Zone,TDZ),直接抛错:
js
console.log(b) // ReferenceError: Cannot access 'b' before initialization
let b = 2
console.log(c) // ReferenceError: Cannot access 'c' before initialization
const c = 3
TDZ 的存在让"先使用后声明"这种错误在开发期就暴露,而不是静静地拿到一个 undefined。
三、重复声明与重新赋值
| 行为 | var | let | const |
|---|---|---|---|
| 重复声明同名变量 | ✅ 允许 | ❌ 报错 | ❌ 报错 |
| 重新赋值 | ✅ | ✅ | ❌ |
| 声明时必须初始化 | ❌ | ❌ | ✅ |
js
// var 允许重复声明,后面的覆盖前面的
var a = 1
var a = 2
console.log(a) // 2
// let 不允许在同一作用域重复声明
let b = 1
let b = 2 // SyntaxError: Identifier 'b' has already been declared
// const 声明即赋值,且不能再指向别的值
const c = 1
c = 2 // TypeError: Assignment to constant variable
const d // SyntaxError: Missing initializer in const declaration
四、const 不等于"值不可变"
这是最容易误解的一点。const 锁的是绑定(引用),不是里面的内容。对原始类型,它确实不可变;对引用类型,属性照样能改:
js
const arr = [1, 2, 3]
arr.push(4) // ✅ 可以,修改的是数组内容
console.log(arr) // [1, 2, 3, 4]
arr = [5] // ❌ TypeError,不能重新指向新数组
const obj = { count: 1 }
obj.count = 2 // ✅ 可以
obj = {} // ❌ TypeError
想要真正冻结对象,需要 Object.freeze(注意它是浅冻结):
js
const obj = Object.freeze({ count: 1 })
obj.count = 2
console.log(obj.count) // 1,静默失败(严格模式抛错)
五、全局声明时挂到哪
在全局作用域下声明:
var会把变量挂到全局对象window上,成为它的属性。let/const不会挂到window,它们存在于全局的词法环境中。
js
var a = 1
let b = 2
const c = 3
window.a // 1
window.b // undefined
window.c // undefined
这带来一个实际差异:var 声明的全局变量会成为 window 的属性,可能覆盖已有属性;let / const 更"干净"。另外在同一全局下,let 声明的名字如果和 window 上已有的不可配置属性同名,会直接报错。
六、怎么选:一条简单的规则
- 默认用
const。绝大多数变量声明后不需要换指向,用它最安全,也向读代码的人表达"这里不会变"。 - 需要重新赋值时用
let,比如循环计数器、累加器、状态变量。 var基本不用。只在需要兼容极老环境或要利用"函数作用域提升"的特殊写法时才考虑,新代码里应避免。
js
// 推荐写法
const list = getList()
let total = 0
for (const item of list) {
total += item.price
}
速查表
| 维度 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 提升并初始化为 undefined | 提升但不初始化(TDZ) | 提升但不初始化(TDZ) |
| 暂时性死区 | 无 | 有 | 有 |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 必须初始化 | 否 | 否 | 是 |
| 挂到全局对象 | 是 | 否 | 否 |
小结
- 核心区别是作用域:
var认函数,let/const认块。 let/const有暂时性死区,声明前访问直接报错,比var拿到undefined更安全。const锁的是引用,不是内容,对象和数组内部仍可修改。- 全局下
var会挂到window,let/const不会。 - 选型一句话:能
const就const,要变用let,var别用。