深入理解JavaScript预解析机制:变量提升与函数提升详解

作者:半夏

时间:2026-10-25

浏览:218

本文深入解析JavaScript引擎的预解析机制,详细区分变量提升与函数提升的执行差异。通过经典面试题案例,阐明var声明与函数声明在预解析阶段的行为,以及作用域链对最终结果的影响,帮助开发者彻底理解undefined与报错的根源。

文章目录

JavaScript预解析是前端面试中的高频考点,许多开发者对其理解尚不透彻。与多数传统编程语言不同,JavaScript(ES5语法)具有独特的执行机制,其中预解析环节尤为关键。本文将通过具体示例,带你彻底厘清这一核心概念。

提出问题

为了直观理解预解析的影响,我们先观察两个最基础且容易出错的代码片段,请思考它们的运行结果是否符合你的预期?

坑一

<script>
    // 坑一
    console.log(num);
    var num = 10;
</script>

提出问题

上述代码中,console.log(num) 的输出结果为 undefined。这看似反直觉,但别急,我们稍后揭晓原因。

坑二

观察这种函数定义方式,你会发现无论函数调用语句写在函数声明之前还是之后,代码均能正常执行。

// 下面这种函数定义方式,函数的调用在函数前后都可以
        fn();
        function fn(){
            console.log(15);
        }
        //fn();

提出问题

那么,JavaScript中另一种常见的函数定义方式——函数表达式,是否也具备同样的特性呢?

// 下面这种函数定义方式,函数的调用只能在函数后面
        fun();
        var fun = function(){
            console.log(15);
        }
        // fun();

提出问题

当我们将函数调用语句置于函数表达式之前时,控制台会抛出错误。这背后的原因是什么?让我们通过下面的解答来一探究竟。

问题解答

要理解上述两个“坑”产生的根本原因,必须掌握JavaScript引擎的工作流程,即预解析机制。

JavaScript引擎执行JS代码主要分为两个阶段:预解析阶段和代码执行阶段。

  • 在预解析阶段,JS引擎会将当前作用域内所有的 var 声明和 function 声明提升至作用域的最顶端。
  • 在代码执行阶段,引擎严格按照代码书写的顺序,从上至下逐行执行逻辑。

预解析过程具体包含两个子过程:变量预解析(即变量提升)和函数预解析(即函数提升)。

  • 变量提升仅提升变量声明,不提升赋值操作。这意味着声明会被提到作用域顶部,但赋值仍保留在原位。
  • 函数提升则是将整个函数声明语句提升至当前作用域的最前面,使得函数可以在声明之前被调用。

理解了上述原理,你是否对之前的疑惑有了初步解答?如果仍感模糊,请通过下面的代码复现来巩固理解。

// // 坑一
        // console.log(num);
        // var num = 10;
        // 相当于执行了以下代码
        var num; // 所有的 var 提升到当前作用域的最前面,不提升赋值操作。
        console.log(num); //num此时未赋值,所以是undefined。
        num = 10;
// // 坑二
// 下面这种函数定义方式,函数的调用在函数前后都可以
        // fn();
        // function fn(){
        //     console.log(15);
        // }
        // 相当于执行下面代码
        // 函数提升到当前作用域的最前面
        function fn(){
            console.log(15);
        }
        fun();
// 下面这种函数定义方式,函数的调用只能在函数后面
        // fun();
        // var fun = function(){
        //     console.log(15);
        // }
        // 相当于执行了以下代码
        var fun;   // 所有的 var 提升到当前作用域的最前面,不提升赋值操作。
        fun(); // 此时压根没有fun()这个函数,所以会报错
        fun = function(){
            console.log(15);
        }

现在,预解析的过程应该已经清晰可见。

案例练习

案例一

请思考,以下代码的运行结果是多少?

 // 案例一
        var num=10;
        fun();
        function fun(){
            console.log(num);
            var num = 20;
        }
        // 相当于执行了以下代码
        // var num;

        // function fun(){
        //     var num;
        //     console.log(num); //根据作用域链的就近原则,此时num未赋值,所以是undefined
        //     num = 20;
        // }
        // num = 10;
        // fun(); // 调用函数

提出问题

案例二

请思考,以下代码的运行结果是多少?

// 案例二
        var num = 10;
        function fn(){
            console.log(num);
            var num = 20;
            console.log(num);
        }
        fn();
        // 相当于执行了以下代码
        // var num;
        // function fn(){
        //     var num;
        //     console.log(num);   //根据作用域链就近原则,此时num未赋值,所以是undefined
        //     num = 20;
        //     console.log(num);  // 根据作用域链就近原则,num是20
        // }
        // num = 10;
        // fn(); // 调用函数

提出问题

案例三

请思考,以下代码的运行结果是多少?

// 案例三
        var a = 18;
        f1();
        function f1(){
            var b = 9;
            console.log(a);  
            console.log(b);
            var a = '123';
        }
        // // 相当于执行了以下代码
        // var a;
        // function f1() {
        //     var b;
        //     var a;
        //     b = 9;
        //     console.log(a);  //根据作用域链就近原则,此时a未赋值,所以是undefined
        //     console.log(b);  // 9
        //     a = '123';
        // }
        // a = 18;
        // f1();

提出问题

案例四(经典的面试题)

请思考,以下代码的运行结果是多少?

 // 案例四
        f1();
        console.log(c);
        console.log(b);
        console.log(a);
        function f1(){
            var a = b = c = 9;
            // 相当于var a = 9; b = 9 ; c = 9 ; 在javascript中不用var申明直接赋值的变量是全局变量,所以b,c是全局变量
            // 区别于集体声明 var a = 9,b = 9,c = 9; 等价于var a = 9,var b = 9,var c = 9;
            console.log(a);
            console.log(b);
            console.log(c);
        }
        // // 相当于执行了以下代码
        // function f1(){
        //     var a;
        //     b = 9;
        //     c = 9;
        //     a = 9;
        //     console.log(a); // 9
        //     console.log(b); // 9
        //     console.log(c); // 9
        // }
        // f1();  // 函数调用
        // console.log(c);  // 9
        // console.log(b);  // 9
        // console.log(a);  // a是函数里的局部变量,所以函数外没有申明的话会报错

提出问题

结语

通过本节内容的学习,相信你对JavaScript中的预解析机制已有了扎实的掌握。掌握这一知识点,将使你在应对相关面试问题时更加从容自信。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章