比较 var, let, const 在作用域、变量提升 (Hoisting) 和重复声明方面的差异。

同学们,早上好! 咱们今天来聊聊 JavaScript 里声明变量的三剑客: varletconst。 这三个家伙看似简单,但用起来可是大有门道。 理解它们的差异,能让你的代码更健壮,也避免一些意想不到的 Bug。

咱们今天就从作用域、变量提升 (Hoisting) 和重复声明这三个方面,把它们扒个底朝天!

一、作用域: 领地意识的较量

首先,咱们得知道什么是作用域。 简单来说,作用域就是变量可以被访问的“领地”。 变量只能在它所属的作用域内被访问。 想象一下,你的卧室就是你的领地,你的东西只能在卧室里找到。

  • var:函数作用域(Function Scope)或者全局作用域(Global Scope)

    var 是个老家伙了,它的作用域比较宽泛。 如果在函数内部声明 var 变量,那么它就是函数作用域,只能在这个函数内部访问。 如果在函数外部声明 var 变量,那就是全局作用域,整个脚本都能访问。

    function myFunction() {
      var x = 10;
      if (true) {
        var y = 20;  // 仍然是函数作用域
        console.log(x); // 输出 10
      }
      console.log(y); // 输出 20  (y 在函数作用域内)
    }
    
    myFunction();
    // console.log(x); // 报错:x is not defined (x 在函数作用域内)
    // console.log(y); // 报错:y is not defined (y 在函数作用域内)
    
    var z = 30; // 全局作用域
    function anotherFunction() {
      console.log(z); // 输出 30 (z 在全局作用域内)
    }
    anotherFunction();
    console.log(z); // 输出 30 (z 在全局作用域内)

    注意 if 语句里面的 var y = 20,虽然在 if 里面,但 var 的作用域仍然是整个 myFunction 函数。 这也是 var 容易出错的地方。

  • letconst:块级作用域(Block Scope)

    letconst 是后起之秀,它们引入了块级作用域的概念。 块级作用域指的是由花括号 {} 包围的代码块。 letconst 声明的变量只能在这个代码块内部访问。

    function myOtherFunction() {
      let x = 10;
      if (true) {
        let y = 20;
        const z = 30;
        console.log(x); // 输出 10
        console.log(y); // 输出 20
        console.log(z); // 输出 30
      }
      // console.log(y); // 报错:y is not defined (y 在块级作用域内)
      // console.log(z); // 报错:z is not defined (z 在块级作用域内)
      console.log(x); // 输出 10
    }
    
    myOtherFunction();
    // console.log(x); // 报错:x is not defined (x 在函数作用域内)

    可以看到,yz 只能在 if 语句块里面访问。 出了这个块,就找不到它们了。 这使得代码更加清晰,也减少了变量污染的可能性。

    for 循环和 while 循环也是块级作用域:

    for (let i = 0; i < 5; i++) {
      console.log(i); // 输出 0, 1, 2, 3, 4
    }
    // console.log(i); // 报错:i is not defined
    
    while (true) {
      const flag = true;
      break;
    }
    // console.log(flag); // 报错:flag is not defined

    letconst 的块级作用域让变量的管理更加精细。

二、变量提升 (Hoisting): 悄悄地挪移

变量提升是 JavaScript 一个比较让人困惑的特性。 简单来说,就是在代码执行之前,JavaScript 引擎会把变量声明(注意,仅仅是声明,不包括赋值)提升到当前作用域的顶部。

  • var:提升并初始化为 undefined

    var 声明的变量会被提升到作用域顶部,并且会被初始化为 undefined。 这意味着你可以在声明之前使用 var 变量,但它的值是 undefined

    console.log(myVar); // 输出 undefined
    var myVar = 10;
    console.log(myVar); // 输出 10

    虽然代码在 var myVar = 10; 之后才声明 myVar,但是由于变量提升,console.log(myVar) 在执行的时候,myVar 已经被声明了,只不过值是 undefined

    这很容易导致一些意外的 Bug。

  • letconst:提升但未初始化

    letconst 声明的变量也会被提升,但是它们不会被初始化。 这意味着如果你在声明之前使用 letconst 变量,会报错:ReferenceError: Cannot access '...' before initialization

    // console.log(myLet); // 报错:ReferenceError: Cannot access 'myLet' before initialization
    let myLet = 20;
    console.log(myLet); // 输出 20
    
    // console.log(myConst); // 报错:ReferenceError: Cannot access 'myConst' before initialization
    const myConst = 30;
    console.log(myConst); // 输出 30

    这种现象被称为“暂时性死区”(Temporal Dead Zone,简称 TDZ)。 变量在 TDZ 中,是无法访问的。

    TDZ 的存在,可以帮助我们更早地发现错误,避免一些潜在的问题。

三、重复声明: 谁能忍受花心大萝卜?

重复声明指的是在同一个作用域内,使用相同的名称声明多个变量。

  • var:允许重复声明

    var 允许在同一个作用域内重复声明变量。 后面的声明会覆盖前面的声明。

    var myVar = 10;
    var myVar = 20; // 覆盖了之前的声明
    console.log(myVar); // 输出 20

    这很容易导致代码逻辑混乱,因为你可能不小心覆盖了之前的变量。

  • letconst:不允许重复声明

    letconst 不允许在同一个作用域内重复声明变量。 如果尝试这样做,会报错:SyntaxError: Identifier '...' has already been declared

    let myLet = 10;
    // let myLet = 20; // 报错:SyntaxError: Identifier 'myLet' has already been declared
    
    const myConst = 20;
    // const myConst = 30; // 报错:SyntaxError: Identifier 'myConst' has already been declared

    这可以避免意外的变量覆盖,让代码更加健壮。

四、const 的特殊性: 承诺一旦许下,永不改变

const 除了拥有 let 的所有特性之外,还有一个特殊的特性:它声明的是常量,一旦赋值,就不能再修改。

const myConst = 40;
// myConst = 50; // 报错:TypeError: Assignment to constant variable.
console.log(myConst); // 输出 40

但是,需要注意的是,const 声明的常量,如果是一个对象或者数组,那么常量存储的是对象或者数组的引用。 你可以修改对象或者数组的内容,但是不能修改常量指向的引用。

const myObject = {
  name: "Alice",
  age: 30
};

myObject.age = 31; // 可以修改对象的内容
console.log(myObject); // 输出 { name: "Alice", age: 31 }

// myObject = { name: "Bob", age: 25 }; // 报错:TypeError: Assignment to constant variable.

同样,对于数组:

const myArray = [1, 2, 3];
myArray.push(4); // 可以修改数组的内容
console.log(myArray); // 输出 [1, 2, 3, 4]

// myArray = [4, 5, 6]; // 报错:TypeError: Assignment to constant variable.

所以,const 并不是说变量的值完全不能变,而是说变量指向的内存地址不能变。

五、总结: 三剑客的对比

为了方便大家记忆,我们用一个表格来总结一下 varletconst 的区别:

特性 var let const
作用域 函数作用域/全局作用域 块级作用域 块级作用域
变量提升 提升并初始化为 undefined 提升但未初始化(TDZ) 提升但未初始化(TDZ)
重复声明 允许 不允许 不允许
修改变量值 允许 允许 不允许 (对于基本类型), 对于对象和数组可以修改内容,但不能修改引用

六、最佳实践: 如何选择?

那么,在实际开发中,我们应该如何选择使用哪个关键字呢?

  • 优先使用 const: 如果变量的值在初始化之后不会再改变,那么应该使用 const。 这可以明确地告诉其他开发者,这个变量是一个常量,不要修改它。 这样做可以提高代码的可读性和可维护性。

  • 如果变量的值需要改变,使用 let: 如果变量的值在初始化之后需要改变,那么应该使用 let

  • 尽量避免使用 varvar 的作用域比较宽泛,容易导致一些意想不到的 Bug。 在现代 JavaScript 开发中,应该尽量避免使用 var

举个例子:

// 循环计数器,值会改变,使用 let
for (let i = 0; i < 10; i++) {
  console.log(i);
}

// 常量,不会改变,使用 const
const PI = 3.14159;

// 需要改变的对象,使用 let
let user = {
  name: "Alice",
  age: 30
};
user.age = 31;
console.log(user);

// 函数内部需要使用的变量,使用 let 或 const
function calculateArea(radius) {
  const area = PI * radius * radius; // area 的值不会改变,使用 const
  return area;
}

console.log(calculateArea(5));

总而言之,constlet 提供了更严格的作用域规则,可以帮助我们编写更清晰、更健壮的代码。 尽量避免使用 var,拥抱 constlet 吧!

好了,今天的讲座就到这里。 希望大家能够掌握 varletconst 的区别,并在实际开发中灵活运用。 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注