resin-blog
首页归档简历规划关于
...
赣ICP备2026011201号-1

js的预编译

2026-05-25· 前端

image.png

前言

随着对js一步步地深入,预编译——神奇的js规则出现了。预编译有函数有的预编译和全局的预编译,相信在读完这篇文章后,以后在面试官恶心你的时候,可以从容应对。

预先知识

函数声明 function fn(){} 函数表达式 let fn = function(){} var 和 函数声明有提升

函数中的预编译

当函数被调用时: 1.创建函数的执行上下文对象 AO {Activation Object}

2.找形参和变量声明(var),将形参和变量名作为AO的属性,值为undefined

3.将实参和形参统一(实参赋值给形参,覆盖undefined)

4.在函数体内找函数声明,将函数名作为AO的属性名,值赋予函数体(function xxx(){}) 这4步走完,开始慢慢执行代码!

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

调用 fn(1) → 立刻进入预编译 4 步! 具体分析如下:

  1. 第一步:创建 AO,收集 形参 + var 变量 AO 一开始是空对象。 我们在函数里找:
  • 形参:a
  • var 声明:var a、var b、var d
  • 注意:只收集名字,不看赋值! 所以:
AO = {
  a: undefined,
  b: undefined,
  d: undefined
}
  1. 第二步:实参赋值给形参 你调用的是 fn(1),所以把 1 赋值给形参 a。 AO 更新:
AO = {
  a: 1,      // 被实参覆盖
  b: undefined,
  d: undefined
}
  1. 第三步:找函数声明,覆盖 AO 属性

这是最关键、最容易懵的一步!

JS 会扫描整个函数体,找到所有 function xxx(){} 这种声明:

function a(){}  // 这是函数声明!
function d(){}  // 这也是函数声明!

函数声明优先级 > 形参 > var 变量

所以:

  • a 被覆盖成函数
  • d 被覆盖成函数 AO 变成最终预编译结果:
AO = {
  a: function a(){},  // 函数覆盖了 1
  b: undefined,
  d: function d(){}   // 函数覆盖了 undefined
}
  • ✔ 预编译到此结束!
  • ✔ 代码还没开始执行
  • ✔ 但所有变量最终 “一开始的值” 已经确定

现在开始 逐行执行代码 执行时,AO 会动态变化

第 1 行:console.log (a);
当前 AO:
a: function a(){}
输出:
function a(){}

第 2 行:var a = 123;
把 123 赋值给 a
AO 变成:
a: 123

第 3 行:console.log (a);
输出:
123

第 4 行:function a (){}
这是函数声明,预编译已经处理过,执行阶段直接跳过!

第 5 行:console.log (a);
a 还是 123
输出:
123

第 6 行:var b = function (){}
b 赋值为一个函数
AO:
b: function(){}

第 7 行:console.log (b);
输出:
function(){}

第 8 行:function d (){}
预编译已经处理,跳过

第 9 行:var d = a;
a 现在是 123
把 123 赋值给 d
AO:
d: 123

第 10 行:console.log (d);
输出:
123

最终输出结果

function a(){}
123
123
function(){}
123

最终 AO 变化轨迹:

AO 初始:{a:undef, b:undef, d:undef}
实参赋值:{a:1, b:undef, d:undef}
函数声明覆盖:{a:function, b:undef, d:function}
执行 a=123:{a:123, b:undef, d:function}
执行 b=function:{a:123, b:function, d:function}
执行 d=a:{a:123, b:function, d:123}

全局的预编译(GO 对象)

全局预编译三步流程

  1. 创建全局 GO 对象
  2. 扫描所有var变量声明、函数声明,变量初始值undefined
  3. 函数声明直接赋值函数体,覆盖默认 undefined

注意:全局没有形参、没有实参赋值这一步

优先级同函数预编译

函数声明 > var 变量声明

举个例子:

global = 100
function fu(){
    console.log(global);
     global = 200
    console.log(global);
    var global = 300
 }
 fu()
 console.log(global);
 var global;
  1. 第一步:全局预编译(创建 GO) 找var,function声明
global = 100
function fu(){}
console.log(global)
var global;
找到了
var global
function fu(){}

所以 GO 全局对象 预编译后:

GO = {
  global: undefined,
  fu: function fu(){}
}
  1. 第二步:开始执行全局代码
global = 100
变成
GO = {
  global: 100,
  fu: function(){}
}

function fu(){} 已经预编译过,跳过

执行 fu() → 进入函数,开始函数预编译!

  1. 第三步:函数 fu 内部预编译(创建 AO) 函数执行前一瞬间,做 4 步预编译:
  • 创建 AO
  • 找形参 + var 声明
  • 函数里只有:var global
  • 无形参,不赋值
  • 找函数声明(这里没有)

所以 AO 预编译结果:

AO = {
  global: undefined
}

函数里有 var global → 会创建自己 AO 的 global,不会去访问全局的 global

  1. 第四步:执行函数内部代码
1. console.log(global)
去 AO 里找 global → undefined
2. global = 200
AO = { global:200 }
3. console.log(global)
输出 200
4. var global = 300
AO.global = 300
  1. 第五步:函数执行完毕,回到全局 函数里的 AO 销毁,不影响全局 GO!

全局 GO 里的 global 依然是:

global:100

最终输出

undefined
200
100

课后例题

好了,现在你应该学会预编译了。接下来写几道例题吧!

var num = 10;
function test() {
    console.log(num);
    num = 20;
    console.log(num);
    var num = 30;
    console.log(num);
}
test();
console.log(num);

undefined 20 30 10

评论

← Agent的祖宗十八代事件循环 →

On this page

  • 前言
  • 预先知识
  • 函数中的预编译
  • 全局的预编译(GO 对象)
  • 全局预编译三步流程
  • 优先级同函数预编译
  • 课后例题