写出更清晰的代码!let、const 和 var 的深度对比与实战技巧

79 阅读7分钟

一、JavaScript 中 constlet 和 var 的区别

作为现代 JavaScript 开发者,掌握变量声明的基本知识是非常重要的。你可能已经听说过 constletvar,但它们有什么不同呢?今天,我们就来聊聊这三者之间的区别,帮助你更清晰地理解它们的用法。


1. var:传统的变量声明方式

var 是 JavaScript 中最早的变量声明方式,它可以在函数或全局作用域中定义变量。虽然它仍然有效,但由于它的一些问题,逐渐被 letconst 替代。

主要特点:

  • 作用域var 声明的变量是函数作用域的。如果在函数外部声明,它会变成一个全局变量。
  • 变量提升var 声明的变量会被提升到函数或全局的顶部,但初始化的赋值仍然保持在原来的位置。也就是说,你可以在变量声明之前使用它,但值会是 undefined
  • 可以重复声明:在同一作用域内,var 允许重复声明相同名字的变量。
console.log(a); // undefined
var a = 10;
console.log(a); // 10

var a = 20; // 允许重复声明
console.log(a); // 20

总结: 由于 var 存在提升和作用域问题,现代开发中通常不推荐使用它,尤其是在 ES6 引入了 letconst 之后。


2. let:块级作用域的变量

let 是 ES6 引入的,它解决了 var 的作用域问题,支持块级作用域。这意味着它只在代码块内有效,比如在 iffor 等控制结构中。

主要特点:

  • 作用域let 声明的变量是块级作用域的,只在代码块内有效。
  • 变量提升let 也会进行变量提升,但不会像 var 那样被初始化为 undefined,而是处于“暂时性死区”(Temporal Dead Zone,TDZ)。在声明之前访问会抛出 ReferenceError 错误。
  • 不能重复声明:在同一作用域内,let 不允许重复声明变量。
let b = 10;
if (true) {
  let b = 20; // 块级作用域中的变量
  console.log(b); // 20
}
console.log(b); // 10

总结: let 是用于声明那些会变化的变量,它比 var 更加安全和灵活,推荐在现代 JavaScript 开发中使用。


3. const:常量声明

const 也是 ES6 引入的,表示一个常量。声明后,变量的值不能再改变。

主要特点:

  • 作用域const 也具有块级作用域
  • 常量const 声明的变量必须在声明时就初始化,并且一旦赋值后就不能修改。对于对象和数组,虽然引用不能改变,但对象的内容是可以修改的。
  • 变量提升:和 let 一样,const 会提升到作用域的顶部,但同样会处于“暂时性死区”,在声明之前不能访问。
const c = 30;
console.log(c); // 30

// 不能重新赋值
// c = 40; // 报错: Assignment to constant variable.

const obj = { name: "Alice" };
obj.name = "Bob"; // 可以修改对象的属性
console.log(obj.name); // "Bob"

二、JavaScript 中 constlet 和 var 的进阶技巧

1. 块级作用域的精妙使用(let 和 const

由于 letconst 都有块级作用域,你可以利用这一点来控制变量的生命周期,从而减少作用域污染。

避免作用域污染

function foo() {
  if (true) {
    let x = 10;  // 只在 if 块内有效
    const y = 20;  // 只在 if 块内有效
    console.log(x, y); // 10, 20
  }
  // console.log(x); // ReferenceError: x is not defined
  // console.log(y); // ReferenceError: y is not defined
}
foo();
  • let 和 const 可以帮助你避免变量泄露到外部作用域,减少变量冲突的可能性,尤其是在大型项目中至关重要。

2. 在循环中使用 let 和 const(避免闭包问题)

当你使用 var 在循环中声明变量时,往往会遇到闭包问题,因为 var 声明的变量是函数作用域,而循环内的异步操作会共享同一个变量。

使用 let 来解决闭包问题

// 使用 var 会导致异步操作引用的是同一个 i 值
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // 3 3 3
  }, 1000);
}

// 使用 let 可以让每次循环都捕获到不同的 i 值
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // 0 1 2
  }, 1000);
}
  • let 使得每次循环都有自己独立的作用域,因此每个异步回调都会捕获当时的 i 值,而不是在所有回调中共享一个 i

3. const 用于引用类型的对象(深度不可变)

虽然 const 可以保证引用的不可变性,但是它不能阻止引用类型(如对象和数组)的内容被修改。因此,我们可以结合 Object.freeze() 来创建深度不可变的对象。

确保对象内容不可变

const person = Object.freeze({
  name: 'Alice',
  age: 30
});

// person.name = 'Bob'; // TypeError: Cannot assign to read only property 'name' of object
console.log(person.name); // "Alice"
  • 使用 Object.freeze() 会冻结对象,防止修改对象的属性。注意,Object.freeze() 只对对象的第一层属性有效,嵌套的对象依然可以修改。

结合 deep-freeze 实现深度冻结

你可以创建一个递归冻结的工具函数,确保嵌套对象也不可变:

function deepFreeze(obj) {
  Object.freeze(obj);
  Object.getOwnPropertyNames(obj).forEach(function (prop) {
    if (obj[prop] !== null && typeof obj[prop] === 'object') {
      deepFreeze(obj[prop]);
    }
  });
  return obj;
}

const obj = deepFreeze({
  name: 'Alice',
  address: { city: 'Wonderland' }
});

obj.address.city = 'Dreamland'; // Cannot modify 'address.city'
console.log(obj.address.city); // "Wonderland"
  • deepFreeze 确保了对象及其所有嵌套对象都不可变。

4. 提高代码可读性:const + 解构赋值

结合 const 和解构赋值,可以使代码更简洁且更具可读性,尤其在处理数组和对象时。

对象解构

const person = { name: 'Alice', age: 25, city: 'Wonderland' };
const { name, age } = person;

console.log(name); // Alice
console.log(age); // 25
  • 使用解构可以快速提取对象中的特定属性,并赋值给局部变量,避免多次重复访问对象属性。

数组解构

const numbers = [10, 20, 30];
const [first, second] = numbers;

console.log(first); // 10
console.log(second); // 20
  • 对数组进行解构时,能够直接获取数组中的元素并赋值给变量,减少代码冗余。

5. let 和 const 与 for 循环中的优化

使用 letconst 可以优化传统 for 循环和 forEach 的性能和可读性,尤其是在处理大量数据时。

for 循环

const numbers = [1, 2, 3, 4, 5];
let sum = 0;

for (let i = 0; i < numbers.length; i++) {
  sum += numbers[i];
}

console.log(sum); // 15
  • let 可以确保每次循环都有一个独立的 i 值。对于高效地处理大型数据集时,let 和 const 提供了比 var 更好的性能和更清晰的代码。

6. const 用于优化常量声明

为了使代码更清晰且减少错误,使用 const 来声明所有不会变的常量,尤其是在代码中使用“魔法数字”或字符串时。

使用 const 来声明常量

const TAX_RATE = 0.07;
const MAX_USERS = 100;

function calculatePrice(price) {
  return price * (1 + TAX_RATE);
}
  • 通过为常量使用 const,可以增加代码的可维护性,并减少后续修改常量值时可能带来的潜在错误。

总结: const 适用于声明那些不需要重新赋值的变量,尤其是对于常量和不会变化的引用类型(如对象和数组)非常有用。


总结

  • var:函数作用域、变量提升、可以重复声明。现在不推荐使用。
  • let:块级作用域、变量提升、不允许重复声明。推荐用于那些需要修改值的变量。
  • const:块级作用域、常量、不允许重新赋值。推荐用于声明常量和不需要重新赋值的变量。
  • let 和 const 具备块级作用域:可以帮助你避免作用域污染,尤其是在循环和条件语句中。
  • 闭包问题:使用 let 在循环中避免了 var 导致的异步闭包问题。
  • const 用于引用类型时的不可变性const 保证了变量的引用不变,但不保证引用类型的内容不可变。使用 Object.freeze() 可以进一步确保对象不可修改。
  • 解构赋值:结合 const 和解构赋值,使代码更加简洁、可读和易于维护。

最佳实践

  1. 优先使用 const:如果变量值不需要修改,使用 const 可以提高代码的可读性和可维护性。
  2. 使用 let:当变量的值需要改变时,使用 let
  3. 避免使用 varvar 是旧式的声明方式,尽量避免使用。

希望这篇文章能够帮助你更好地理解 constletvar 的区别,让你在编写 JavaScript 代码时更加得心应手!