JavaScript let 和 const(建议收藏)

JavaScript let 和 const:从入门到精通

在学习 JavaScript 的过程中,你一定遇到过变量声明的问题。尤其是在使用 var 声明变量时,常常会因为作用域混乱、重复声明等问题导致程序出错。随着 ES6 的发布,JavaScript 引入了 letconst,为变量声明带来了更安全、更清晰的解决方案。今天,我们就来深入聊聊这两个关键字,帮助你彻底掌握它们的用法和背后的设计思想。


变量声明的演进:为什么需要 let 和 const?

在 ES6 之前,JavaScript 只有 var 一种变量声明方式。虽然它能完成基本任务,但存在几个明显缺陷:

  • 变量提升(Hoisting)var 声明的变量会被“提升”到作用域顶部,即使在声明前使用也不会报错,只是值为 undefined
  • 函数作用域 vs 块级作用域var 是函数作用域,而现代编程更倾向于使用块级作用域(如 if、for、{} 内部),以避免意外污染。
  • 重复声明var 允许同名变量重复声明,容易引发逻辑错误。

举个例子:

console.log(a); // 输出: undefined(没有报错)
var a = 10;

这看似“正常”,实则隐藏了风险。letconst 的出现,正是为了解决这些问题,让代码更可预测、更安全。


let:块级作用域的守护者

let 是 ES6 引入的块级作用域变量声明方式。它解决了 var 的两大痛点:作用域混乱和变量提升。

块级作用域详解

在 JavaScript 中,块(Block) 指用 {} 包裹的一段代码,例如 ifforwhiletry-catch 等语句内部。

if (true) {
  let message = "Hello from block!";
  console.log(message); // 输出: Hello from block!
}

// console.log(message); // 报错!ReferenceError: message is not defined

✅ 这里 message 只在 if 块内有效,块外无法访问。这就是块级作用域的体现。

不能重复声明

let 不允许在同一作用域中重复声明变量:

let name = "Alice";
// let name = "Bob"; // 报错!SyntaxError: Identifier 'name' has already been declared

这避免了因误写导致的变量覆盖问题。

暂时性死区(Temporal Dead Zone)

let 声明的变量存在“暂时性死区”——从声明开始到初始化完成之间,变量不可访问。

console.log(age); // 报错!ReferenceError: Cannot access 'age' before initialization
let age = 25;

这比 var 的“提升但值为 undefined”更安全,因为开发者无法在声明前访问变量。


const:声明不可变值的首选

const 用于声明常量,即一旦赋值就不能再修改。但要注意:const 保证的是变量引用不变,而不是值不变

基本用法

const PI = 3.14159;
// PI = 3.14; // 报错!TypeError: Assignment to constant variable.
console.log(PI); // 输出: 3.14159

对象和数组的 const 陷阱

很多人误以为 const 声明的变量完全不可变。实际上,它只禁止重新赋值引用。

const user = {
  name: "Bob",
  age: 30
};

user.name = "Alice"; // ✅ 允许:修改对象属性
console.log(user.name); // 输出: Alice

// user = { name: "Charlie" }; // ❌ 报错!不能重新赋值引用

同样适用于数组:

const numbers = [1, 2, 3];
numbers.push(4); // ✅ 允许:修改数组内容
console.log(numbers); // 输出: [1, 2, 3, 4]

// numbers = [10, 20]; // ❌ 报错!不能重新赋值

📌 小贴士:如果你希望对象或数组完全不可变,可以使用 Object.freeze() 或配合不可变数据结构库(如 Immer)。


let 和 const 的选择策略

面对 letconst,如何选择?一个简单原则:

优先使用 const,只有需要重新赋值时才用 let

这符合“最小权限原则”:只赋予变量必要的可变性。

实际案例:用户登录状态管理

// ✅ 使用 const 声明常量
const API_URL = "https://api.example.com/v1";
const MAX_RETRY = 3;

// ✅ 使用 const 声明初始状态
const initialState = {
  isLoggedIn: false,
  token: null,
  username: ""
};

// ✅ 使用 let 处理需要变化的状态
let retryCount = 0;

function login(username, password) {
  // 模拟登录逻辑
  if (username === "admin" && password === "123456") {
    // ✅ 重新赋值,使用 let
    retryCount = 0;
    return {
      isLoggedIn: true,
      token: "abc123",
      username
    };
  } else {
    // ✅ 递增计数,使用 let
    retryCount++;
    if (retryCount >= MAX_RETRY) {
      console.log("登录失败次数过多!");
    }
    return initialState;
  }
}

这个例子清晰地展示了 const 用于配置、初始状态,let 用于动态变化的变量。


常见误区与注意事项

误区一:const 变量不能修改任何内容

如前所述,const 只防止重新赋值引用,不冻结对象或数组内容。务必区分“引用不变”和“值不变”。

误区二:let 和 const 没有性能差异

在现代 JavaScript 引擎中,letconst 的性能基本一致。选择它们应基于语义而非性能。

误区三:const 不能用于函数声明

const 可以声明函数表达式,但不能用于函数声明(因为函数声明会提升):

// ❌ 错误:函数声明不能用 const
// const function myFunc() {}

// ✅ 正确:函数表达式可以
const myFunc = function () {
  console.log("Hello");
};

// ✅ 或者使用箭头函数
const greet = () => console.log("Hi");

实用技巧:在循环中使用 let 和 const

for 循环中,let 的块级作用域特性非常有用,尤其在事件绑定或闭包中。

// ❌ 使用 var 的常见陷阱
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 输出: 3, 3, 3(不是预期的 0, 1, 2)
  }, 100);
}

// ✅ 使用 let 的正确方式
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 输出: 0, 1, 2(正确)
  }, 100);
}

为什么?因为 let 在每次循环迭代中创建一个独立的绑定,而 var 共享同一个变量。


总结:让代码更安全、更清晰

JavaScript let 和 const 的引入,是现代 JavaScript 的重要里程碑。它们不仅修复了 var 的历史遗留问题,更引导开发者写出更健壮、更易维护的代码。

  • let 提供块级作用域,避免变量提升和作用域污染。
  • const 强制声明常量,提升代码可读性和安全性。
  • 优先使用 const,仅在必要时使用 let,这是良好的编码习惯。

记住:好的变量声明,是写出好代码的第一步

当你在项目中看到一段用 const 声明的配置项、用 let 管理状态的逻辑时,你会感受到代码的秩序与美感。

从今天起,告别 var,拥抱 letconst,让你的 JavaScript 代码更现代、更安全、更专业。