JavaScript 变量提升机制详解:从 var 到 let/const 的演变

作者:小括号

时间:2026-10-30

浏览:150

本文深入解析 JavaScript 中 var 声明的变量提升机制,区分可访问性与可用性,并对比 ES6 引入的 let 和 const 如何消除变量提升及引入暂时性死区,帮助开发者规避常见陷阱并养成规范编码习惯。

文章目录

JavaScript 变量提升机制概述

JavaScript 中存在变量提升(Hoisting)机制,这通常被视为语言设计上的遗留问题或实现副作用。该机制允许变量在未声明的情况下被访问,或者在声明之前就被使用。这一特性常令新手困惑,甚至许多资深开发者也易产生误解。然而,随着 ES6 标准引入 let 和 const 关键字,变量提升现象在块级作用域中已不再存在。

var 声明的变量提升行为

一、未声明变量直接使用


function test() {
  alert(notDefined);
}
test(); // ?

此时程序会直接报错,这是符合预期的行为。

JavaScript 变量提升机制概述

二、变量声明置于代码末尾


function test() {
  alert(declaredButNotAssigned); // undefined
  var declaredButNotAssigned;
}
test();

运行结果输出 undefined。虽然代码不再报错并能继续执行,但变量的值往往并非程序员所预期的结果。

三、变量声明置于末尾并同时赋值


function test() {
  alert(declaredAndAssigned); // undefined
  var declaredAndAssigned = 1;
}
test();

结果与二一致,显然赋值操作不会导致输出 1。

上述第二、三种情况均涉及变量提升(Hoisting),现对其进行简要定义。

变量提升是指:在指定作用域内,尽管从代码书写顺序上看是“先使用后声明”,但在运行时,变量的“可访问性”会被提升至当前作用域的顶部,其初始值为 undefined,此时变量并不具备“可用性”。

此处强调“代码顺序”与“运行顺序”的区别,因为大多数代码是顺序执行的,两者通常一致,这也符合人类的思维逻辑。例如,拥有 C 语言经验的程序员


#include <stdio.h>
int main() {
  int x = 1;
  printf("%d, ", x); // 1
}

编写如下代码:先声明整型变量 x 并赋值为 1,随后输出。此时代码顺序与运行顺序一致,程序正常运行。

如果将顺序颠倒,


#include <stdio.h>
int main() {
  printf("%d, ", x); // error
  int x = 1;
}

在 C 语言中,编译器将无法通过检查并报错。但在 JavaScript 中,如前文第二、三例所示,这种颠倒是被允许的。

因此,具备 C 语言经验的程序员深知变量必须“先声明后使用”,否则会导致错误。而在 JavaScript 中,由于存在变量提升现象,允许“先使用后声明”,这导致 C 语言的经验迁移到 JS 开发时会产生困惑。

函数表达式与函数声明的提升差异

四、函数表达式同样存在变量提升


function test() {
  alert(func); // undefined
  var func = function() {};
}
test();

然而,如果试图调用该函数,则无法实现。


function test() {
  alert(func); // undefined
  func(); // 报异常
  var func = function() {};
}
test();

结果显示 func 为 undefined,调用 func 会抛出异常。这对应前文提到的“可访问性”与“可用性”概念,具体表现如下。

可访问性:执行 alert(func) 会输出 undefined,代码可以运行,说明 func 是可访问的。

可用性:执行 func() 会抛出异常,无法正常调用,说明 func 不具备可用性。

第二、三、四种情况均使用 var 声明变量。在 JavaScript 中,函数声明也会发生提升,但这里的“变量”较为特殊,其类型为 function(可作为函数、方法或构造器)。其标识符名称同样会被提升至当前作用域的顶部。

五、函数声明的名称也会提升至当前作用域顶部


function test() {
  alert(f1); // function
  f1(); // "called"
  function f1() {
    alert('called');
  }
}
test();

可以看到,尽管函数 f1 的声明位于代码末尾,但在使用前调用 alert(f1) 和 f1() 均能正常执行,这表明 f1 同时具备了可访问性和可用性。

变量提升的面试题解析

前文已述,变量提升(Hoisting)并无实际益处,属于语言设计中的缺陷,良好的编码习惯应是“先声明后使用”。这一特性也常出现在各大公司的技术面试题中。

面试题 1:


// 写出以下代码的运行结果
var a = 1;
function fn() {
  if (!a) {
    var a = 2;
  }
  alert(a); // ?
}
fn();

面试题 2:


// 写出以下代码的运行结果
var a = 1;
function fn() {
  a = 2;
  return;
  function a() {}
}
fn();
alert(a); // ?

ES6 let/const 对变量提升的终结

然而,随着 ES6 标准中 let 和 const 的引入,这一切已发生改变。在 ES6 及后续版本中,除全局变量外,其他变量均应使用 let 或 const 声明。将 var 替换为 let 后,变量提升现象便不复存在。


function test() {
  alert(declaredButNotAssigned1); // 报异常
  alert(declaredButNotAssigned2); // 报异常
  alert(func); // 报异常
 
  let declaredButNotAssigned1;
  let declaredButNotAssigned2 = true;
  let func = function() {};
}
test();

这强制程序员养成良好习惯,变量必须“先声明再使用”,否则将直接报错。

以下摘录自 MDN 关于 let 不再发生变量提升的描述:

代码如下:

In ECMAScript 6, let does not hoist the variable to the top of the block. If you reference a variable in a block before the let declaration for that variable is encountered, this results in a ReferenceError, because the variable is in a "temporal dead zone" from the start of the block until the declaration is processed.

使用 let 声明变量后,typeof 操作符也不再安全。


if (condition) {
  alert(typeof num); // Error!
  let num = 100;
}
 

过去可以使用 typeof == 'undefined' 来判断是否引入了某个库,例如 jQuery。


// 判断jQuery是否引入了
if (typeof $ !== 'undefined') {
  // do something
}...

如果未引入 jQuery,变量 $ 未声明,该判断语句不会报错且不影响后续代码执行;但如果使用 let 声明变量,则会抛出错误。

以上即为本文的全部内容,希望对大家有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章