js的预编译
· 前端

前言
随着对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 步! 具体分析如下:
- 第一步:创建 AO,收集 形参 + var 变量 AO 一开始是空对象。 我们在函数里找:
- 形参:a
- var 声明:var a、var b、var d
- 注意:只收集名字,不看赋值! 所以:
AO = {
a: undefined,
b: undefined,
d: undefined
}
- 第二步:实参赋值给形参 你调用的是 fn(1),所以把 1 赋值给形参 a。 AO 更新:
AO = {
a: 1, // 被实参覆盖
b: undefined,
d: undefined
}
- 第三步:找函数声明,覆盖 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 对象)
全局预编译三步流程
- 创建全局 GO 对象
- 扫描所有var变量声明、函数声明,变量初始值undefined
- 函数声明直接赋值函数体,覆盖默认 undefined
注意:全局没有形参、没有实参赋值这一步
优先级同函数预编译
函数声明 > var 变量声明
举个例子:
global = 100
function fu(){
console.log(global);
global = 200
console.log(global);
var global = 300
}
fu()
console.log(global);
var global;
- 第一步:全局预编译(创建 GO) 找var,function声明
global = 100
function fu(){}
console.log(global)
var global;
找到了
var global
function fu(){}
所以 GO 全局对象 预编译后:
GO = {
global: undefined,
fu: function fu(){}
}
- 第二步:开始执行全局代码
global = 100
变成
GO = {
global: 100,
fu: function(){}
}
function fu(){} 已经预编译过,跳过
执行 fu() → 进入函数,开始函数预编译!
- 第三步:函数 fu 内部预编译(创建 AO) 函数执行前一瞬间,做 4 步预编译:
- 创建 AO
- 找形参 + var 声明
- 函数里只有:var global
- 无形参,不赋值
- 找函数声明(这里没有)
所以 AO 预编译结果:
AO = {
global: undefined
}
函数里有 var global → 会创建自己 AO 的 global,不会去访问全局的 global
- 第四步:执行函数内部代码
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
- 第五步:函数执行完毕,回到全局 函数里的 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