JS预编译的江湖秘辛:V8引擎的“幕后黑手”

143 阅读3分钟

JS预编译的江湖秘辛:V8引擎的“幕后黑手”

前言

你以为 JS 代码是写完就直接跑的吗?太天真了!其实在 JS 真正执行之前,V8 引擎早已在幕后悄悄“预编译”了一遍,把变量、函数、上下文全都安排得明明白白。今天就带大家用轻松幽默的方式,揭开 JS 预编译的神秘面纱。

一、V8的预编译流程:一场“宫斗剧”

V8 引擎遇到 JS 代码,第一步不是直接执行,而是:

  1. 生成 AST(抽象语法树),像皇帝批阅奏折,先把结构理清楚。
  2. 生成字节码,准备好“兵马”随时开战。
  3. 预编译:把变量、函数、上下文都登记造册。
  4. 最后才是解释执行,正式“开打”。

编译全局 -> 执行全局 -> 编译函数 -> 执行函数

二、全局编译过程:变量和函数的“户口本”

全局预编译时,V8 会做三件大事:

  1. 创建全局执行上下文对象(GO),相当于给所有变量和函数找个家。
  2. 找到所有全局变量声明,把变量名作为 GO 的属性,值先设为 undefined
  3. 找到所有全局函数声明,函数名作为 GO 的属性,值直接赋给函数体(注意,函数比变量优先)。

举个栗子:

var a = 1;
function fn() {}

预编译后,GO 里是:

GO = {
  a: undefined // 变量先登记,后赋值
  fn: function fn() {}
}

三、函数体编译过程:参数、变量、函数“三国杀”

每次函数被调用,都会经历一次“函数体编译”,流程如下:

  1. 创建函数的执行上下文对象(AO)。
  2. 找到所有参数和变量声明,把名字作为 AO 的属性,值设为 undefined
  3. 实参和形参统一,参数赋值。
  4. 在函数体里找函数声明,函数名作为 AO 的属性名,值赋给函数体(函数提升,优先级最高)。

比如:

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

预编译 AO 过程:

  • 先登记参数 a,AO = { a: undefined }
  • 再登记变量 a,AO = { a: undefined }
  • 实参和形参统一,赋值给参数 a, AO = { a:1 }
  • 最后函数声明提升,AO = { a: function a() {} }
  • 执行到 var a = 123 时,a 才被赋值为 123

所以输出:

[Function: a]

是不是很魔幻?

四、函数声明 VS 函数表达式:谁才是“正宫娘娘”?

  • 函数声明:预编译时就被提升,AO/GO 里直接有名字。
  • 函数表达式:只是普通变量,预编译时值为 undefined,只有执行到那一行才赋值。
function a() {} // 声明,提前有名有实
var b = function() {} // 表达式,先有名,后有实

五、实战案例分析

例1:变量和函数的“宫斗”

function fn(a){
    console.log(a); // [Function: a]
    var a = 123
    console.log(a); // 123
    function a() {}
    var b = function() {}
    console.log(b); // [Function: b]
    function d() {}
    var d = a
    console.log(d) // 123
}
fn(1);

执行结果:

[Function: a]
123
[Function: b]
123

例2:参数、变量、函数的“三国杀”

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

执行结果:

1
2
2

例3:全局变量的“真假身份”

global = 100
function fn() {
    console.log(global); // undefined
    global = 200
    console.log(global); // 200
    var global = 300
}
fn()
var global

执行结果

undefined
200

六、总结

JS 预编译就像一场宫廷大戏,变量、参数、函数各有自己的“上位”时机。理解了这些幕后机制,你就能写出更健壮、更优雅的 JS 代码。下次再遇到“变量提升”“函数提升”这些面试题,记得想想背后的“预编译江湖”!


希望这篇文章能让你在 JS 的世界里,既能笑出声,也能学到真本事!