预编译按执行节点可分为两个环节:
1、JavaScript代码执行之前
2、函数代码执行之前
->过程都是先筛选一遍变量(包括var/let/…/形参/实参/function/if语句中的),再执行一遍
->为了分析,筛选过程用一个全局对象GO(Global Obj)存放全局的变量状态,AO(Activetion Object,执行期对象)存放函数内的变量状态
-> 例子:
<script>
console.log(a,b)
a = 1;
function b(c, d){
function f(){};
c = 9;
var a = 8;
console.log(a)
e = 5;
console.log(e)
}
b(3)
var a = function(){};
console.log(a)
var b = 2;
console.log(b)
console.log(e)
</script>
-> 开始:
1、JavaScript代码执行之前
(1)筛选
①按顺序筛选var声明语句,变量存放到GO,状态为undefined,
未声明直接赋值的(如a=2)同样按undefined存放,重复声明的取代之前的;
GO:{
a: undefined,
b: undefined
}
②再走一遍筛选出function函数体语句(注意是非函数调用语句),如:function b(){},函数名存放到GO,状态为函数体本身。与①重复的取代,把①的变量的undefined赋值为函数体
GO:{
a: undefined,
b: function b(c,d){...}
}
(2)执行
注:执行语句在筛选过程先全部忽略不计
③筛选完从头开始执行,赋值、console之类的语句,函数体在这里忽略不计如function b(c, d){…}
console.log(a,b) // 打印 undefined ƒ b(c, d){...}
a = 1;
/*
GO:{
a: 1,
b: function b(c,d){...}
}
*/
function b(c, d){ //忽略
function f(){};
c = 9;
var a = 8;
console.log(a)
e = 5;
console.log(e)
}
b(3) //遇到函数调用,进入函数进行第二环节↓
var a = function(){};
console.log(a)
var b = 2;
console.log(b)
console.log(e)
执行到函数调用语句(如fn())进入第二个环节↓
这时的
GO:{
a: 1,
b: function b(c,d){...}
}
2、函数代码执行之前
函数内部和“JavaScript代码执行之前”类似
创建一个AO对象
(1)筛选
④将函数形参放入AO,状态为undefined:AO:{c:undefined,d:undefined}
⑤筛选内部的var声明语句,变量存放到AO,状态为undefined:AO:{c:undefined,d:undefined,a:undefined}
⑥筛选未声明直接赋值的语句(如e=5),进行变量提升属于全局变量,按全局变量保存到GO,状态为undefined;若函数中声明过,按undefined存放于AO
AO:{c:undefined,d:undefined,a:undefined}
,
GO:{
a: 1,
b: function b(c,d){...},
e:undefined,
}
⑦实参赋给形参的状态:AO:{c:3,d:undefined,a:undefined}
⑧筛选内部的函数体声明语句,函数名存放到AO,状态为函数体:
AO:{
c:3,
d:undefined,
a:undefined,
f:function f(){}
}
(2)执行
⑨ 与③的执行一样,另:当要执行的变量在函数中不存在时,向函数外找
function b(c, d){
function f(){}; //忽略
c = 9;
/*
AO:{
c:9,
d:undefined,
a:undefined,
f:function f(){}
}
*/
var a = 8;
/*
AO:{
c:9,
d:undefined,
a:8,
f:function f(){}
}
*/
console.log(a) //打印 8
e = 5;
/*
GO:{
a: 1,
b: function b(c,d){...},
e: 5,
}*/
console.log(e) //打印 5
}
函数调用执行完继续执行下面的语句
console.log(a,b) // 打印 undefined ƒ b(c, d){...}
a = 1;
/*
GO:{
a: 1,
b: function b(c,d){...}
}
*/
function b(c, d){ //忽略
function f(){};
c = 9;
var a = 8;
console.log(a)
e = 5;
console.log(e)
}
b(3) //遇到函数调用,进入函数进行第二环节↓
var a = function(){};
/*
GO:{
a: function(){},
b: function b(c,d){...}
}
*/
console.log(a) //打印 ƒ (){}
var b = 2;
/*
GO:{
a: function(){},
b: 2
}
*/
console.log(b) //打印 2
console.log(e) //打印 5
完整打印:
undefined ƒ b(c, d){
function f(){};
c = 9;
var a = 8;
console.log(a)
e = 5;
console.log(e)
}
8
5
ƒ (){}
2
5
注:①连等赋值
let a = b = 2;
// 相当于
b = 2; // 未申明自动提升为全局变量
let a = b;
②if语句中有定义也同步拿出来
③let与var的区别在于 let存在块级作用域不存在变量提升
为了完全理解预编译过程找了好几篇文章才得到较完整的信息,用这个思路能正确地分析得到编译结果