理解 `var`, `let`, `const` 的变量提升(Hoisting)与作用域

好嘞!各位观众老爷们,今天咱们就来聊聊 JavaScript 里那些让人又爱又恨的“妖魔鬼怪”——varletconst 以及它们背后的“隐身术”——变量提升(Hoisting)和作用域!准备好你的爆米花,咱们开讲啦!🍿

开场白:JavaScript 的“魔法”世界

JavaScript,一门充满魔力的语言,它赋予了网页动态的灵魂,让交互变得生动有趣。但就像所有魔法一样,它也有一些“小秘密”,初学者很容易被这些“小秘密”绊倒。今天我们要揭秘的就是其中之一:变量的声明、提升和作用域。

想象一下,你走进一家魔法商店,琳琅满目的商品让你眼花缭乱。你还没付款呢,就看到老板已经在给你准备打包了!这感觉是不是有点懵?JavaScript 的变量提升就像这样,它会在你还没声明变量之前,就“偷偷”地把变量“提升”到作用域的顶部。

第一幕:var——“老顽童”的变量提升

var,JavaScript 的老牌变量声明方式,就像一位经验丰富的“老顽童”,性格洒脱,但也有些“任性”。

console.log(variable); // 输出:undefined
var variable = "Hello, world!";
console.log(variable); // 输出:Hello, world!

看到没?在声明 variable 之前,我们竟然可以访问它!而且还没报错,只是输出了 undefined。这就是 var 的变量提升:

  • 声明提升: JavaScript 引擎在执行代码之前,会先扫描整个作用域,将所有使用 var 声明的变量“提升”到作用域的顶部。
  • 初始化未提升: 仅仅是声明被提升了,赋值操作仍然留在原地。所以,在声明之前访问变量,得到的是 undefined,而不是报错。

我们可以用一个表格来总结一下 var 的特性:

特性 说明
声明方式 var variableName;
变量提升 声明会被提升到作用域顶部,但赋值不会。
初始化 默认初始化为 undefined
作用域 函数作用域(在函数内部声明的变量只能在函数内部访问)/全局作用域(在函数外部声明的变量可以在任何地方访问)
重复声明 允许在同一作用域内重复声明同一个变量。

举个例子,如果你写了这样的代码:

function myFunction() {
  console.log(myVar); // 输出:undefined
  var myVar = "Inside the function";
  console.log(myVar); // 输出:Inside the function
}

myFunction();
console.log(myVar); // 报错:myVar is not defined (如果是在函数外部声明的var变量,这里会报错)

myFunction 内部,myVar 被提升到了函数顶部,但赋值操作仍然在原地。因此,第一个 console.log 输出的是 undefined

第二幕:letconst——“现代骑士”的变量声明

letconst 是 ES6 引入的新变量声明方式,它们就像两位“现代骑士”,严谨、可靠,但也更加“挑剔”。

// console.log(letVariable); // 报错:Cannot access 'letVariable' before initialization
let letVariable = "This is let";
console.log(letVariable); // 输出:This is let

// console.log(constVariable); // 报错:Cannot access 'constVariable' before initialization
const constVariable = "This is const";
console.log(constVariable); // 输出:This is const

注意到了吗?如果我们尝试在声明 letVariableconstVariable 之前访问它们,会直接报错!这就是 letconst 的“暂时性死区”(Temporal Dead Zone,TDZ)。

  • 暂时性死区(TDZ): 从作用域顶部到声明语句之间的区域,就是变量的“暂时性死区”。在这个区域内,访问变量会报错。

虽然 letconst 也有变量提升,但它们的提升方式和 var 不同。它们只是将变量“提升”到作用域顶部,但不会进行初始化。因此,在声明之前访问它们,会报错。

用表格总结一下 letconst 的特性:

特性 let const
声明方式 let variableName; const constantName;
变量提升 声明会被提升到作用域顶部,但不会初始化。 声明会被提升到作用域顶部,但不会初始化。
初始化 必须在使用前初始化。 必须在声明时初始化。
作用域 块级作用域(在代码块 {} 内部声明的变量只能在该代码块内部访问)。 块级作用域(在代码块 {} 内部声明的变量只能在该代码块内部访问)。
重复声明 不允许在同一作用域内重复声明同一个变量。 不允许在同一作用域内重复声明同一个变量。
可变性 声明后可以重新赋值。 声明后不能重新赋值(但如果 const 声明的是对象,则可以修改对象的属性)。

举个例子:

function myFunction() {
  // console.log(myLet); // 报错:Cannot access 'myLet' before initialization
  let myLet = "Inside the function";
  console.log(myLet); // 输出:Inside the function

  // console.log(myConst); // 报错:Cannot access 'myConst' before initialization
  const myConst = "This is a constant";
  console.log(myConst); // 输出:This is a constant

  // myConst = "Trying to change a constant"; // 报错:Assignment to constant variable.
}

myFunction();
// console.log(myLet); // 报错:myLet is not defined
// console.log(myConst); // 报错:myConst is not defined

myFunction 内部,myLetmyConst 都被提升了,但由于暂时性死区的存在,在声明之前访问它们会报错。const 声明的变量必须在声明时初始化,并且之后不能重新赋值。

第三幕:作用域——变量的“领地”

作用域,顾名思义,就是变量的“领地”,决定了变量在哪里可以被访问。JavaScript 中主要有三种作用域:

  1. 全局作用域: 在函数外部声明的变量,拥有全局作用域。这意味着它们可以在代码的任何地方被访问。
  2. 函数作用域: 在函数内部声明的变量,拥有函数作用域。这意味着它们只能在该函数内部被访问。
  3. 块级作用域: 使用 letconst 声明的变量,拥有块级作用域。这意味着它们只能在声明它们的代码块 {} 内部被访问。

用一个表格来总结一下:

作用域 说明
全局作用域 在函数外部声明的变量,可以在代码的任何地方被访问。
函数作用域 在函数内部声明的变量,只能在该函数内部被访问。
块级作用域 使用 letconst 声明的变量,只能在声明它们的代码块 {} 内部被访问。

举个例子:

let globalVariable = "I'm global"; // 全局变量

function myFunction() {
  let functionVariable = "I'm inside the function"; // 函数变量

  if (true) {
    let blockVariable = "I'm inside the block"; // 块级变量
    console.log(globalVariable); // 输出:I'm global
    console.log(functionVariable); // 输出:I'm inside the function
    console.log(blockVariable); // 输出:I'm inside the block
  }

  console.log(globalVariable); // 输出:I'm global
  console.log(functionVariable); // 输出:I'm inside the function
  // console.log(blockVariable); // 报错:blockVariable is not defined
}

myFunction();
console.log(globalVariable); // 输出:I'm global
// console.log(functionVariable); // 报错:functionVariable is not defined
// console.log(blockVariable); // 报错:blockVariable is not defined

在这个例子中,globalVariable 可以在任何地方被访问。functionVariable 只能在 myFunction 内部被访问。blockVariable 只能在 if 语句的代码块内部被访问。

第四幕:变量遮蔽(Variable Shadowing)——“李代桃僵”的戏法

变量遮蔽是指在内部作用域声明了一个与外部作用域同名的变量,内部作用域的变量会“遮蔽”外部作用域的变量。

let outerVariable = "Outer";

function myFunction() {
  let outerVariable = "Inner"; // 遮蔽了外部的 outerVariable
  console.log(outerVariable); // 输出:Inner
}

myFunction();
console.log(outerVariable); // 输出:Outer

在这个例子中,myFunction 内部声明了一个与外部 outerVariable 同名的变量。在 myFunction 内部,outerVariable 指的是内部声明的变量,而不是外部的变量。

总结:选择合适的变量声明方式

了解了 varletconst 的特性和作用域,我们就可以更好地选择合适的变量声明方式:

  • const 优先使用 const,声明那些声明后不会被修改的变量。这有助于提高代码的可读性和可维护性。
  • let 如果变量需要被修改,使用 let
  • var 尽量避免使用 varvar 的变量提升和函数作用域容易导致一些意想不到的问题。

用一句话总结:const 优先,let 其次,var 慎用。

尾声:掌握魔法,创造奇迹

理解 varletconst 的变量提升和作用域,就像掌握了 JavaScript 的一门“魔法”。掌握了这门魔法,你就可以写出更清晰、更可维护的代码,创造出更精彩的网页应用!

希望今天的讲解能帮助你更好地理解 JavaScript 的变量声明和作用域。记住,学习编程就像学习魔法,需要不断地练习和探索。祝你在 JavaScript 的魔法世界里,创造出属于你的奇迹!🎉

附加说明:面试常见问题

  • 什么是变量提升?varletconst 的变量提升有什么区别?
  • 什么是暂时性死区(TDZ)?
  • varletconst 的作用域有什么区别?
  • 什么是变量遮蔽?
  • const 声明的对象可以修改属性吗?为什么?
  • 在循环中使用 varlet 有什么区别?(这是一个很经典的面试题,可以深入探讨)

这些问题都是面试中经常出现的,希望你能够认真思考,并用自己的语言表达出来。

最后,别忘了点赞、评论、转发,让更多的小伙伴一起学习 JavaScript!我们下期再见!👋

发表回复

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