← 返回题目列表

let、const 和 var 有什么区别?

高频 简单 第 5 / 28 题 更新于 2026/07/27
ES6letconstvar作用域

简化版

var 是函数作用域,有变量提升,可以重复声明;letconst 是块级作用域,有暂时性死区,不能在同一作用域重复声明。const 只保证绑定不能重新赋值,不保证对象内容不可变。

详细版

主要区别:

  • 作用域:var 是函数作用域,let/const 是块级作用域。
  • 提升:三者都会被提升,但 let/const 在声明前访问会触发暂时性死区错误。
  • 重复声明:var 允许,let/const 不允许。
  • 赋值:const 声明时必须初始化,之后不能重新绑定。
const obj = { a: 1 };
obj.a = 2; // 可以
obj = {}; // 报错

因为 const 锁的是变量绑定,不是对象内部属性。工程里优先用 const,需要重新赋值时用 let,尽量不用 var

完整版教学

一、var 的函数作用域问题

var 不认块级作用域:

if (true) {
  var a = 1;
}
console.log(a); // 1

这在循环和异步回调里很容易出错。let 会把变量限制在块内,让代码更符合直觉。

二、暂时性死区是什么

let/const 声明的变量在作用域开始时已经存在,但在执行到声明语句前不能访问,这段区域叫暂时性死区。

console.log(a); // ReferenceError
let a = 1;

它的价值是减少“先使用后声明”的混乱代码。

三、const 和不可变不是一回事

const 表示变量名不能再指向另一个值。如果值是对象,对象内部仍然可以修改。

要让对象更接近不可变,可以用 Object.freeze,但它默认只冻结第一层。真正不可变数据通常需要约定、工具库或结构共享方案。

四、最佳实践

优先 const 能表达“这个绑定不会变”,让读代码的人更放心。只有确实需要重新赋值,比如循环计数、状态累积时才用 letvar 主要出现在老代码里,新代码尽量避免。

五、面试追问与工程落地

面试官常会追问“let/const 有没有变量提升”。更准确的答案是:有提升,但不能在声明前访问,因为处于暂时性死区。不要简单说“let 不提升”,这会被继续追问。声明在词法环境中已经被创建,只是初始化前不可用。

还可能问循环里的 let 为什么能解决异步输出问题。for (let i...) 每轮循环都会创建新的词法绑定,所以每个回调捕获的是不同的 i;var 只有一个函数级 i,回调共享同一个变量。

工程里 const 优先并不是为了对象不可变,而是为了表达绑定不变,减少读代码时的心智负担。如果后面不重新赋值,就用 const;需要累加、重新指向、状态切换时再用 let。

六、从环境记录解释作用域与循环闭包

var 声明绑定在函数或全局 var 环境中,letconst 则绑定在当前词法环境中。进入块级作用域时,词法绑定已经存在但尚未初始化,从作用域起点到声明执行之间就是暂时性死区;这不是“变量不存在”,而是访问未初始化绑定会抛出 ReferenceError

const jobs = []
for (let i = 0; i < 3; i++) {
  jobs.push(() => i)
}
console.log(jobs.map(fn => fn())) // [0, 1, 2]

for 循环使用 let 时,每轮迭代会创建对应的词法绑定,因此 3 个闭包分别捕获 0、1、2。改用 var 时通常只有 1 个共享绑定,循环结束后值为 3,三个闭包都会读到 3。

对比项varletconst
作用域函数/全局块级块级
声明前访问undefinedTDZ 报错TDZ 报错
同作用域重复声明允许部分情况不允许不允许
重新赋值允许允许不允许

浏览器经典脚本顶层的 var 可能成为全局对象属性,而顶层 letconst 不会;ES 模块又拥有自己的模块作用域。讨论“全局变量”时必须先说明代码运行在经典脚本、模块还是函数包装环境。

const 保证的是绑定不能指向另一个值,不会递归冻结对象;需要不可变数据时还要配合设计约束或冻结方案。

七、常见误区与追问

  • 误区:let 和 const 不会变量提升。 声明会建立绑定,只是在初始化前处于暂时性死区,不能提前访问。
  • 误区:const 声明的对象完全不可变。 不能给变量重新赋值,但对象内部属性仍可修改。
  • 误区:var 只有“可以重复声明”这一个问题。 它还缺少块级作用域,并可能造成循环闭包共享同一绑定。
  • 追问:为什么 typeof x 在 TDZ 中也会报错? typeof 只有对未解析标识符较特殊;已经存在但未初始化的词法绑定仍触发 ReferenceError
  • 追问:循环中的 let 为什么能得到不同值? 每轮迭代会建立新的对应绑定,闭包捕获的不是同一个共享变量。
  • 追问:const 为什么必须初始化? 绑定一旦初始化就不能重新赋值,因此普通声明必须同时给出初值。
  • 追问:日常代码如何选择三者? 默认使用 const,确需重新赋值时用 let,新代码通常不使用 var

八、加强记忆

这题记住三句话:var 管函数,let/const 管块;let/const 有暂时性死区;const 锁绑定,不锁对象内容。

答题时先区分绑定的创建与初始化,再说明作用域、TDZ、重赋值和每轮循环绑定,结论会更完整。 不要只背“优先 const”,还要能解释这种选择如何缩小可变状态范围。