解释一下 var、let 和 const 之间的区别

33 阅读3分钟

解释一下 var、let 和 const 之间的区别

核心答案

varletconst 是 JavaScript 中声明变量的三种方式,主要区别在于:

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升有(初始化为 undefined)有(暂时性死区 TDZ)有(暂时性死区 TDZ)
重复声明允许不允许不允许
重新赋值允许允许不允许
全局对象属性是(挂载到 window)

深入解析

1. 作用域差异

var 是函数作用域:只有函数能创建新的作用域,iffor 等代码块不会限制 var 的访问范围。

let/const 是块级作用域:任何 {} 代码块都能创建独立的作用域。

2. 变量提升与暂时性死区(TDZ)

三者都存在变量提升,但行为不同:

  • var:声明和初始化都被提升,初始值为 undefined
  • let/const:只有声明被提升,但在声明语句执行前不能访问(处于 TDZ)

TDZ 的本质是:从块级作用域开始到变量声明语句之间的区域,访问该变量会抛出 ReferenceError

3. 底层机制

在 V8 引擎中,letconst 声明的变量在编译阶段会被放入一个特殊的「词法环境」中,并标记为「未初始化」。只有执行到声明语句时才会被初始化。

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

面试技巧

可能的追问方向

  1. 「为什么 let 能解决 for 循环的闭包问题?」

    • 每次迭代都会创建新的块级作用域,let 声明的变量在每个作用域中都是独立的副本
  2. 「TDZ 的设计目的是什么?」

    • 帮助开发者发现错误(在声明前使用变量通常是 bug)
    • 使 const 的语义更合理(避免先是 undefined 再变成真正的值)
  3. 「如何让 const 声明的对象完全不可变?」

    • Object.freeze() 浅冻结
    • 递归冻结实现深度不可变
    • 使用 Immutable.js 等库
  4. 「实际开发中如何选择?」

    • 默认使用 const
    • 需要重新赋值时使用 let
    • 避免使用 var(除非维护老代码)

展示深度理解

  • 提及 ES6 规范中 let/const 引入的「词法环境」概念
  • 说明 TDZ 是运行时概念,不是语法限制
  • 讨论 var 在历史上存在的原因和现代 JavaScript 的演进

一句话总结

var 是函数作用域且会提升为 undefined,letconst 是块级作用域且有暂时性死区,const 额外限制不能重新赋值——现代开发中优先使用 const,需要重新赋值时用 let,避免使用 var