Back

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

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 声明的对象中添加、删除或更改属性,在数组中添加、删除或更改元素。变量始终指向内存中的同一个对象或数组。

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

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

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.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay