12k
All articles

JavaScript 变量声明:理解 var、let 和 const

对比 ES6 中 var、let 和 const 在变量作用域、变量提升及暂时性死区方面的差异,编写能预防错误并清晰表达意图的 JavaScript 代码。

OpenReplay Team
OpenReplay Team
JavaScript 变量声明:理解 var、let 和 const

现代 JavaScript 开发要求在 JavaScript 变量声明方面保持清晰——然而许多开发者仍然在 varletconst 之间的选择上感到困惑。理解 ES6 变量作用域不仅仅是记住规则;而是编写能够传达意图并在错误发生前预防它们的代码。

核心要点

  • 块级作用域(let/const)比函数作用域(var)提供更好的变量封装
  • 暂时性死区(Temporal Dead Zone)防止在初始化前使用变量
  • const 防止重新赋值,但不阻止对象和数组的修改
  • 现代 JavaScript 默认使用 const,必要时使用 let,避免使用 var

理解 JavaScript 的三种声明关键字

JavaScript 提供三种声明变量的方式,每种在作用域、提升(hoisting)和重新赋值方面都有不同的行为。你的选择向 JavaScript 引擎和其他阅读你代码的开发者传达了不同的意图。

块级作用域 vs 函数作用域

现代 ES6 变量作用域与传统模式之间的根本区别在于变量的封装方式:

function processData() {
    if (true) {
        var x = 1;  // Function-scoped
        let y = 2;  // Block-scoped
        const z = 3; // Block-scoped
    }
    console.log(x); // 1 (accessible)
    console.log(y); // ReferenceError
    console.log(z); // ReferenceError
}

使用 var 时,变量会泄漏出代码块但保留在函数内。letconst 都遵守块级边界——任何花括号都会创建一个新的作用域,包括循环、条件语句和普通代码块中的花括号。

暂时性死区详解

使用 letconstJavaScript 变量声明会被提升,但在到达声明语句之前保持未初始化状态。这就创建了暂时性死区(TDZ):

console.log(myVar);   // undefined (hoisted, initialized)
console.log(myLet);   // ReferenceError (TDZ)
console.log(myConst); // ReferenceError (TDZ)

var myVar = 1;
let myLet = 2;
const myConst = 3;

暂时性死区防止了 var 行为经常导致的微妙错误。你不能意外地在变量正确初始化之前使用它——引擎会立即抛出错误。

重新赋值 vs 修改

许多开发者忽略的一个关键区别:const 防止重新赋值,而非修改:

const user = { name: 'Alice' };
user.name = 'Bob';          // Allowed (mutation)
user = { name: 'Charlie' }; // TypeError (reassignment)

const scores = [95, 87];
scores.push(91);            // Allowed
scores = [100, 100];        // TypeError

要实现真正的不可变性,需要将 constObject.freeze() 或不可变数据库结合使用。声明关键字本身不会使你的数据不可变——它使绑定不可变。

变量声明的现代最佳实践

当今的 JavaScript 代码库遵循明确的层级结构:

  1. 默认使用 const:用于不会被重新赋值的值。这包括大多数变量,甚至是你会修改的对象和数组。

  2. 在需要重新赋值时使用 let:循环计数器、累加器变量以及确实需要重新绑定的值。

  3. 在新代码中避免使用 var:没有任何现代用例中 varlet 更有优势。

现代工具强制执行这些模式。ESLint 的 no-var 规则和 prefer-const 规则会自动捕获违规。TypeScript 将 ES6 变量作用域规则视为基础,使块级作用域成为默认的思维模型。

框架注意事项

React hooks 依赖于 JavaScript 变量声明的可预测行为:

function Counter() {
    const [count, setCount] = useState(0); // const for the binding
    
    // Wrong: Can't reassign
    // count = count + 1;
    
    // Right: Use the setter
    setCount(count + 1);
}

Vue 的 Composition API 和其他现代框架遵循类似的模式,其中 const 占主导地位,因为响应式系统通过方法而非重新赋值来处理更新。

为什么遗留代码仍然使用 var

你会在旧代码库中遇到 var,这是历史原因。在 ES6(2015)之前,它是唯一的选择。迁移需要仔细测试,因为:

  • 在循环中将 var 改为 let 可能会修复错误或引入新错误,取决于闭包行为
  • 一些遗留代码故意利用 var 的函数作用域提升
  • 旧的构建工具可能无法正确转译 ES6 变量作用域

重构时,使用像 jscodeshift 这样的自动化工具,并配合全面的测试覆盖。不要手动转换数千个声明——让工具处理机械性的更改,而你负责验证行为。

总结

现代代码中的 JavaScript 变量声明遵循一个简单的规则:默认使用 const,需要重新赋值时使用 let,永远不要使用 var。这不是跟随潮流——而是编写清晰表达意图的代码,并利用 ES6 变量作用域来预防整类错误。暂时性死区、块级作用域和重新赋值规则不是障碍——它们是引导你编写更易维护的 JavaScript 的护栏。

常见问题

如果我计划修改数组和对象的内容,可以使用 const 吗?

可以,const 只防止重新赋值变量本身,不阻止修改内容。你可以在用 const 声明的对象中添加、删除或更改属性,在数组中添加、删除或更改元素。变量始终指向内存中的同一个对象或数组。

如果我尝试在声明之前访问 let 或 const 变量会发生什么?

由于暂时性死区,你会得到 ReferenceError。与 var 在声明前访问时返回 undefined 不同,let 和 const 变量在代码到达其声明行之前处于未初始化状态。

我应该将现有代码中的所有 var 声明重构为 let 或 const 吗?

只有在仔细测试的情况下才可以。虽然通常是有益的,但在循环中将 var 改为 let 可能会改变闭包行为。使用具有全面测试覆盖的自动化重构工具,而不是手动更改,以避免引入错误。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.