let 和 var 声明变量有什么不同?一文讲清作用域、提升与闭包陷阱
在 JavaScript 中,var 是 ES5 时代唯一的变量声明方式 ,而 let 是 ES6 引入的新语法,两者看似只是关键字替换,实则在作用域 、变量提升、重复声明、全局对象绑定等方面存在本质差异 ,理解这些差异,是写出可预测 、少 Bug 的现代 JavaScript 代码的基础。
作用域不同:函数作用域 vs 块级作用域
这是两者最核心的区别 。
var 声明的变量属于函数作用域(function scope),即变量在整个函数内部都可见 ,甚至能穿透 if、for 等语句块:
function test() {
if (true) {
var a = 1;
}
console.log(a); // 1,块外仍可访问
}
let 声明的变量属于块级作用域(block scope),只在最近的一对花括号 内有效:

function test() {
if (true) {
let b = 1;
}
console.log(b); // ReferenceError: b is not defined
}
块级作用域带来的直接好处是避免了变量“泄漏”到外层 ,也让 for 循环中的计数器行为更符合直觉:
for (var i = 0; i < 3; i++) {}
console.log(i); // 3,var 泄漏到外层
for (let j = 0; j < 3; j++) {}
console.log(j); // ReferenceError
变量提升行为不同
两者都会被“提升 ”,但表现截然不同。
var 声明会被提升到所在函数(或全局)作用域的顶部,且初始值为 undefined,因此可以在声明之前访问:
console.log(x); // undefined var x = 10;
let 同样存在提升 ,但变量会进入暂时性死区(Temporal Dead Zone,TDZ),在声明语句执行之前访问它,会直接抛出 ReferenceError:

console.log(y); // ReferenceError: Cannot access 'y' before initialization let y = 10;
TDZ 的存在让“先用后声明”的错误更早暴露 ,而不是悄悄得到一个 undefined,这对排查问题非常有帮助。
重复声明规则不同
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
这一限制能有效防止无意间的变量覆盖,尤其在多人协作的大型项目中意义重大 。

与全局对象的关系不同
在浏览器环境下,顶层 var 声明的变量会成为 window 对象的属性,而 let 不会:
var g1 = 1; let g2 = 2; console.log(window.g1); // 1 console.log(window.g2); // undefined
这意味着 let 声明的全局变量不会污染全局对象 ,也不会与 window 上已有的属性(如 name、length)发生冲突。
闭包与循环中的经典陷阱
var 在循环中只有一个共享的绑定,配合异步回调时会出现“所有回调拿到同一个值”的经典问题:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 输出:3 3 3
而 let 在每次循环迭代时都会创建一个新的绑定,因此能正确捕获当次的值:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 输出:0 1 2
这是 let 常被推荐用于循环计数器的直接原因。
总结对比表
| 特性 | var |
let |
|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 |
| 变量提升 | 提升,初始值 undefined |
提升但存在 TDZ ,访问报错 |
| 重复声明 | 允许 | 不允许 |
| 绑定全局对象 | 是(浏览器中为 window) |
否 |
| 循环闭包 | 共享同一绑定 | 每次迭代新绑定 |
let 在作用域控制 、错误提示和闭包行为上都优于 var,在现代 JavaScript 开发中,应优先使用 let(需要常量时使用 const) ,仅在维护旧代码或确有特殊需求时才考虑 var,理解两者的差异,不仅能帮你避开那些隐蔽的作用域与提升陷阱 ,也能让代码更清晰、更安全 。









