深入理解 JavaScript 预编译机制

270 阅读3分钟

在日常开发和面试中,JavaScript 的“预编译”机制(也称为变量提升、函数提升)经常成为考点。很多初学者会被一些看似“诡异”的输出结果困扰,其实背后都离不开 JS 引擎的预编译过程。本文将带你系统梳理 JS 预编译的原理、流程,并结合代码案例,帮助你彻底搞懂这一机制。

一、什么是 JS 预编译?

JavaScript 是一门解释型语言,但现代引擎(如 V8)在执行 JS 代码前,会先进行“编译”阶段。这个阶段主要做两件事:

  1. 变量和函数声明提升:提前收集所有的变量声明(var)和函数声明(function),并在作用域顶部进行初始化。
  2. 创建执行上下文:为全局或函数作用域创建对应的执行上下文对象(ECO),用于存储变量、函数、参数等信息。

简而言之,JS 代码的执行分为两个阶段:预编译阶段执行阶段

二、全局与函数的预编译流程

1. 全局预编译

  • 创建全局执行上下文对象(Global Object,简称 GO)。
  • 找出所有全局变量声明(var),在 GO 上创建同名属性,初始值为 undefined
  • 找出所有全局函数声明,函数名作为 GO 的属性,值为函数体(后声明会覆盖前声明)。

2. 函数预编译

  • 创建函数执行上下文对象(AO)。
  • 形参和变量声明提升,初始值为 undefined
  • 实参赋值给形参。
  • 函数声明提升,函数名作为 AO 属性,值为函数体(后声明会覆盖前声明)。

注意:变量提升只提升声明,不提升赋值;函数声明整体提升,函数表达式只提升变量名。

三、代码案例详解

案例1:变量和函数提升

function fn(a) {
    console.log(a);
    var a = 123;
    console.log(a);
    function a() { }
    var b = function () { }
    console.log(b);
    function d() { }
    var d = a;
    console.log(d);
}
fn(1);

66fe982024004cfbaf8150e117dfe6e4~tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAgU3VuX2xpZ2h0_q75.webp

预编译过程:

  1. 创建 AO:{ a: undefined, b: undefined, d: undefined }
  2. 形参赋值:a = 1
  3. 函数声明提升:a = function a() {}d = function d() {}
  4. 变量声明已存在,不变
  5. 执行阶段依次输出:
  • console.log(a) 输出:function a() {}
  • a = 123console.log(a) 输出:123
  • b = function () {}console.log(b) 输出:function () {}
  • d = aconsole.log(d) 输出:123

案例2:参数、变量、函数同名

function foo(a, b) {
    console.log(a);
    c = 0;
    var c;
    a = 3;
    b = 2;
    console.log(b);
    function b() { }
    console.log(b);
}
foo(1);

执行上下文.png

预编译过程:

  • AO 初始:{ a: 1, b: undefined, c: undefined }
  • 函数声明提升:b = function b() {}
  • 变量声明已存在,不变
  • 执行阶段:
  1. console.log(a) 输出:1
  2. c = 0var c 已声明,赋值为 0
  3. a = 3b = 2
  4. console.log(b) 输出:2
  5. function b() {} 已被 b = 2 覆盖
  6. console.log(b) 输出:2

四、常见面试陷阱与注意点

  1. 函数声明优先于变量声明提升,但变量赋值会覆盖函数声明。
  2. 函数表达式只提升变量名,不提升函数体
  3. 同名参数、变量、函数声明时,最终以最后一次声明为准
  4. 全局变量未用 var 声明时,会自动挂载到 window(或 global)对象上,但不推荐这样做。

六、总结

JS 预编译机制是理解变量提升、函数提升、作用域链等核心概念的基础。掌握预编译流程,不仅能帮助你写出更健壮的代码,还能在面试中游刃有余。建议大家多写多练,遇到输出“诡异”的题目时,先在脑海中模拟一遍预编译过程,答案自然就清晰了。