js预编译(前端面试必背)
总所周知,javascript是一门解释型的脚本语言,其主要的步骤为解释一行,执行一行,但这执行第一行代码之前,javascript会有一个预编译的步骤。
大家有没有发现,有时我们在变量声明的前面使用该变量,不会报错。这种变量提升的情况也是属于预编译的中所做的。
JavaScript的预编译分为全局预编译和局部预编译(函数预编译)
- 创建GO对象(Global Object)全局对象。
- 找变量声明,将变量名作为GO属性名,值赋为undefined。
- 查找函数声明,将函数名作为GO属性,值赋予函数体。
- 创建AO对象(Activation Object)执行期上下文。
- 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined。
- 将实参值赋值给对应形参属性。
- 在函数体里面找函数声明,值赋予函数体。
1 console.log(`1 -> a:${a} \n b:${b} \n c:${c}`) 2 /* 声明 */ 3 var a // 变量声明 4 function b() { 5 // b的函数体 6 } // 函数声明方式 7 var c = function() { 8 // c的函数体 9 } // 函数表达式方式 10 console.log(`2 -> a:${a} \n b:${b} \n c:${c}`) 11 /* 赋值 */ 12 a = 1 13 b = 2 14 c = 3 15 console.log(`3 -> a:${a} \n b:${b} \n c:${c}`)
上面代码声明了变量a,已经用两种函数创建方式创建了函数b和c,并在代码的不同地方打印出abc的值。
猜猜看这3个地方的变量都会是什么。
如果在不知道js预编译的情况下,看到这样的打印结果确实很奇怪,接下来我们来通过全局预编译的步骤来解读下这段代码。
首先位于全局作用域的代码,也就是所有的代码会进行全局预编译。
一、创建一个Go对象。
Go {
}
二、找变量声明,并将该变量名作为上面创建Go对象的属性名,属性值赋值为undefined;变量声明为第3行的变量a,以及第7行的变量c。
三、找函数声明,将函数名作为上面创建GO对象的属性名,属性值赋予函数体;函数声明为第4行的函数b。
Go {
a: undefined,
b: function(){},
c: undefined
}
四、目前预编译已经完成,接下来开始逐行执行代码;第1行打印abc及第三步操作后的Go对象的属性。(a为undefined,b为函数,c为undefined)
五、第2~6行的变量声明与函数声明已经在预编译操作过,第7行的变量c声明在预编译操作过,但是,第7~9行还存在变量赋值的操作即将函数赋值给变量c。此时Go对象为
Go {
a: undefined,
b: function(){},
// c: undefined,
c: function(){},
}
六、接着第10行打印abc即第五步操作后的Go对象的属性。(a为undefined,b为函数,c为函数)
七、执行第11~14行的变量赋值操作,此时Go对象为
Go {
// a: undefined,
// b: function(){},
// c: function(){},
a: 1,
b: 2,
c: 3,
}
八、最后执行第15行打印abc即第七步操作后的Go对象的属性。(a为1,b为2,c为3)
好啦!这下知道控制台打印的值都是为什么了吗。接下来我们看看局部预编译。
局部预编译 ↓
局部预编译发生在没一个函数体中,让我们来看看它的奥秘吧!
1 function func(a){ // 函数声明 2 console.log(`1 -> a:${a} \n b:${b} \n c:${c} \n d:${d}`) 3 var b // 变量声明 4 function c(){} // 函数声明方式 5 var d = function(){} // 函数表达式方式 6 console.log(`2 -> a:${a} \n b:${b} \n c:${c} \n d:${d}`) 7 b = 2 8 c = 3 9 d = 4 10 console.log(`3 -> a:${a} \n b:${b} \n c:${c} \n d:${d}`) 11 } 12 func(1) // 函数调用
上面的代码在全局中声明了一个函数func并且调用,在函数内具有参数a、变量b、函数声明方式c、函数表达式方式d;并在各个阶段打印出abcd的值。
我们来一步步来解释上面控制台所输出的结果。
一、首先这段代码会先经历全局预编译,和一次局部的函数预编译。经历全局预编译创建Go对象。
Go {
func: function(){}
}
二、func函数内部进行局部预编译。第一步创建局部Ao对象。
Go {
func: function(){}
}
Ao {
}
三、找func函数形参和变量声明,将变量和形参名作为AO对象属性名,属性值赋值为undefined;具有形参a、变量声明b、变量声明d。
Go {
func: function(){}
}
Ao {
a: undefined,
b: undefined,
d: undefined,
}
四、将实参值赋值给对应的形参属性。即将第12行函数调用所传入的实参1赋值给a属性。
Go {
func: function(){}
}
Ao {
// a: undefined,
a: 1,
b: undefined,
d: undefined,
}
五、找函数声明,将函数名作为Ao属性名,值赋值为函数体;函数声明方式创建了c。
Go {
func: function(){}
}
Ao {
a: 1,
b: undefined,
c: function(){},
d: undefined,
}
六、此时预编译已经完成,包括全局预编译与局部预编译。第1~11行为函数声明,执行第12行函数调用,然后开始执行函数内部代码,执行第2行,打印abcd即第五步操作后的Ao对象的属性(a为1,b为undefined,c为函数,d为undefined)
七、函数内部第3~5行,其中变量b与d的声明以及函数c的声明在局部预编译已经做过,只剩下第5行的变量d的赋值;此时的Ao对象为。
Go {
func: function(){}
}
Ao {
a: 1,
b: undefined,
c: function(){},
// d: undefined,
d: function(){},
}
八、执行函数内部第6行代码,打印abcd即第七步操作后的Ao对象的属性值。(a为1,b为undefined,c为函数,d为函数)
九、执行函数内部第7~9行的变量赋值操作,此时的Ao对象为。
Go {
func: function(){}
}
Ao {
a: 1,
// b: undefined,
// c: function(){},
// d: function(){},
b: 2,
c: 3,
d: 4,
}
十、最后执行函数内部的第10行,打印abcd的值即第九步操作后的Ao对象属性值。(a为1,b为2,c为3,d为4)