解释一下 var、let 和 const 之间的区别
核心答案
var、let 和 const 是 JavaScript 中声明变量的三种方式,主要区别在于:
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 有(初始化为 undefined) | 有(暂时性死区 TDZ) | 有(暂时性死区 TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 全局对象属性 | 是(挂载到 window) | 否 | 否 |
深入解析
1. 作用域差异
var 是函数作用域:只有函数能创建新的作用域,if、for 等代码块不会限制 var 的访问范围。
let/const 是块级作用域:任何 {} 代码块都能创建独立的作用域。
2. 变量提升与暂时性死区(TDZ)
三者都存在变量提升,但行为不同:
- var:声明和初始化都被提升,初始值为
undefined - let/const:只有声明被提升,但在声明语句执行前不能访问(处于 TDZ)
TDZ 的本质是:从块级作用域开始到变量声明语句之间的区域,访问该变量会抛出 ReferenceError。
3. 底层机制
在 V8 引擎中,let 和 const 声明的变量在编译阶段会被放入一个特殊的「词法环境」中,并标记为「未初始化」。只有执行到声明语句时才会被初始化。
4. const 的「不可变」误区
const 只保证变量绑定不可变(即不能重新赋值),但如果值是引用类型,对象的属性是可以修改的。
代码示例
作用域差异
// var - 函数作用域
function testVar() {
if (true) {
var x = 1;
}
console.log(x); // 1 - 可以访问
}
// let - 块级作用域
function testLet() {
if (true) {
let y = 1;
}
console.log(y); // ReferenceError: y is not defined
}
暂时性死区
console.log(a); // undefined(var 提升)
console.log(b); // ReferenceError: Cannot access 'b' before initialization
var a = 1;
let b = 2;
经典闭包问题
// var 的问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出: 3, 3, 3
// let 解决方案
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// 输出: 0, 1, 2
const 与引用类型
const obj = { name: 'Alice' };
obj.name = 'Bob'; // ✅ 允许修改属性
obj = { name: 'Eve' }; // ❌ TypeError: Assignment to constant variable
// 如果需要完全不可变
const frozen = Object.freeze({ name: 'Alice' });
frozen.name = 'Bob'; // 静默失败(严格模式下报错)
全局对象属性
var globalVar = 'var';
let globalLet = 'let';
console.log(window.globalVar); // 'var'
console.log(window.globalLet); // undefined
面试技巧
可能的追问方向
-
「为什么 let 能解决 for 循环的闭包问题?」
- 每次迭代都会创建新的块级作用域,
let声明的变量在每个作用域中都是独立的副本
- 每次迭代都会创建新的块级作用域,
-
「TDZ 的设计目的是什么?」
- 帮助开发者发现错误(在声明前使用变量通常是 bug)
- 使
const的语义更合理(避免先是 undefined 再变成真正的值)
-
「如何让 const 声明的对象完全不可变?」
Object.freeze()浅冻结- 递归冻结实现深度不可变
- 使用 Immutable.js 等库
-
「实际开发中如何选择?」
- 默认使用
const - 需要重新赋值时使用
let - 避免使用
var(除非维护老代码)
- 默认使用
展示深度理解
- 提及 ES6 规范中
let/const引入的「词法环境」概念 - 说明 TDZ 是运行时概念,不是语法限制
- 讨论
var在历史上存在的原因和现代 JavaScript 的演进
一句话总结
var是函数作用域且会提升为 undefined,let和const是块级作用域且有暂时性死区,const额外限制不能重新赋值——现代开发中优先使用const,需要重新赋值时用let,避免使用var。