变量提升 vs 函数提升

变量提升 vs 函数提升

February 6, 2025

变量提升

使用 var 声明的变量会被提升到当前作用域的顶部,只提升声明,不提升赋值。

letconst 声明的变量不会被提升,会产生暂时性死区(TDL)。

// var 声明,变量提升
console.log(a)  // undefined
var a = 10

// let/cost 声明,变量不提升
console.log(b)  // Uncaught ReferenceError: b is not defined
let b = 100

函数提升

函数声明会被完整的提升到当前作用域顶部,包括函数名函数体

函数表达式不会被提升,因为本质上是变量赋值。

// 函数提升
console.log(foo)  // ƒ foo() { console.log('Hello') }  // 打印结果是函数名和函数体
function foo() {
  console.log('Hello')
}

// var 声明的函数表达式,变量提升
console.log(foo)  // undefined  // 本质是变量提升,只提升声明,所以打印结果是 undefined
var foo = function() {
    console.log('hello')
}

函数提升优先级 > 变量提升优先级

实际上,是先进性函数提升,再进行变量提升的。我们可以测试一下:

console.log(foo);  // ƒ foo() {}  // 输出函数体
function foo() {};
var foo;

console.log(foo);  // ƒ foo() {}  // 输出函数体
var foo;
function foo() {};

但是后面的函数声明会覆盖前面的函数声明,也同样测试下:

console.log(foo)  // ƒ foo() { console.log('2') }  // 输出后面的函数声明
function foo() { console.log('1') }
function foo() { console.log('2') }

我们可以分析下载编译阶段时,JS引擎是如何处理函数声明和变量声明的。

// 以这两个声明为例
console.log(foo);
var foo = 1;
function foo() {
  return "function";
}

// JS 引擎的处理步骤:
// 步骤1:首先提升函数声明
function foo() { return "function"; }

// 步骤2:然后处理变量声明(但因为已有同名函数声明,这步实际上被忽略)
// var foo; // 被忽略!

// 步骤3:执行阶段
console.log(foo); // 输出函数体
foo = 1; // 执行阶段:赋值覆盖之前的函数声明

注意,在最后的执行阶段,变量赋值会覆盖函数声明。

总结一下,要注意的有三点:

  • 函数提升优先级高于变量提升
  • 后面的函数声明会覆盖前面的函数声明
  • 变量赋值会在执行阶段覆盖函数声明

函数提升、变量提升发生在编译阶段,变量赋值发生在执行阶段。理解了这句话就会很好理解上面三点。