首先要知道JavaScript在运行的时候是分为三个阶段:
一、词法语法分析;词法语法分析就是检查JavaScript代码是否有一些低级的语法错误
二、预编译;
三、执行代码;执行代码就是js引擎解析代码,解析一行执行一行
预编译:
下面来详细分析一下:
预编译也分为2个时间点,第一个是在JavaScript代码执行之前,第二个是在函数执行之前。但是JavaScript代码之前之前的预编译只发生一次,函数执行之前的预编译是多次的。
先说一下JavaScript代码执行之前的预编译:
js代码之前,首先会创建一个全局对象,可以理解为window对象,也可以理解为GO(Global Object)对象。然后将所有声明的全局变量、未使用var和let声明的变量放到GO对象中,并且赋值为undefined;然后再将所有的函数声明也放到GO对象中,并且赋值为函数自身的函数体。
举例:
<script>
var a = 1;
console.log(a);
console.log(b);
var b = 10;
function fun (a) {
console.log(b);
var a = b = 2;
var c = 123;
console.log(a);
console.log(b);
}
var a2 = 20
fun(1);
</script>
按照上面所说的来理解,script里边的代码执行之前会创建一个GO对象:
然后将所有声明的全局变量、未使用var和let声明的变量放到GO对象中,并且赋值为undefined。
然后再将所有的函数声明也放到GO对象中,并且赋值为函数自身的函数体。
此时完成了js代码执行之前的预编译过程,代码开始执行,首先是给a进行赋值为1,在GO对象里边也会进行对应的改变:
然后打印a,此时会在GO对象上去找变量a,然后此时的a的值为1,所以console.log(a) 是等于1的。接着打印b,也会去GO对象上找,找到了b的值为undefined,所以console.log(b)是等于undefined。
接着执行到赋值语句:b = 10;此时GO对象里b的值变成了10。
接着下一行代码是一个fun函数,此时不会去执行该函数因为在前面的预编译过程中实际上是被放到了代码的最前端,就是传说中的声明提前,所以忽略掉了。接着给a2进行赋值操作:a2 = 20,GO对象也发生变化:
接着是执行fun函数,如上面说到的另外一个时间点发生的预编译,就是执行函数之前,现在就来说一下函数执行前的预编译是怎么样的。
第一步:AO对象。
第二步:查找形参和变量声明放到AO对象,赋值undefined。
第三步:将实参和形参相统一,就是将实参赋值到形参上。
第四步:查找函数声明放到AO对象并赋值为函数体。
现在继续拿上面的demo来举例说明:
第一步创建AO对象
第二步查找形参和变量声明放到AO对象并赋值为undefined;注意fun函数里边的b是未经var声明的,所以是全局变量,不会被放在fun的AO上。
第三步将实参赋值到形参上
第四步查找函数声明放到AO对象并赋值为函数体,fun函数没有函数声明,所以忽略这一步。
此时函数执行之前的预编译已经完成,并开始执行语句。
首先执行打印变量b,而此时fun的AO里边并没有变量b,所以会去GO对象里边找,此时的GO对象b的值为10,所以第一行代码打印出10;
第二行代码首先要看的是b = 2,然后GO对象里边b的值就被改为2了。
然后b再赋值给a,变量a是属于局部变量a,所以fun的AO对象里边a的值被改为2。
接着下一个赋值语句是c = 123,所以AO对象中c的值被改为了123。
此时再执行console.log(a)的值就是AO对象里边a的值 2;执行console.log(b)的值就是GO对象b的值 2,至此函数fun执行完毕,紧跟着fun的AO也会被销毁。
综上所述,依次打印出来的值为:1,undefined,10,2,2。
注意:
全局window对象 就是 GO对象;
预编译时,变量只是被定义,但是并未初始化(赋值)。而函数声明是整体提升,就是说函数名和函数体都会同时被放在GO/AO对象中;
同名的变量,后面的会覆盖前面的;