这是我参与新手入门的第一篇文章
前言
一道面试题引发的思考
作为面试的常考内容,JavaScript的预编译相信大家已经很熟悉了。本人作为一个已经工作多年的前端工程师,自以为对这部分的内容了然于心。然而在刷面试题的过程中,却不慎翻了车。到底是个什么样的问题呢?让我们来一起看看吧。
问题部分
function m(){
console.log(a1);
console.log(a2);
console.log(b1);
console.log(b2);
if(false){
function b1(){};
var a1 = 100;
}
if(true){
function b2(){};
var a2 = 10;
}
}
m();
如果你的答案是会打印出4个undefined,那么恭喜你,你在预编译这方面的基础,是相当扎实的;如果a1和a2都猜错了,推荐先读一下下文巩固以下基础;如果只是好奇为什么b1和b2也是undefined;请直接跳到文末的“观察并思考”部分进行阅读!
一、JavaScript的预编译的体现
JavaScript的预编译是指JS在运行过程中会将一些变量提前解析出来的行为。如果没有这种行为,那么JS也会和一些其他的语言一样,无法解析一些“先使用,后定义”的变量。
a = 2; // 如果没有预编译,那么这段代码将会报错!
var a; // 在js中实际上是这段代码先被编译的,所以js不会对这段代码报错
但是JS也不是针对所有的变量进行预编译操作,比如使用ES6的let,const声明变量,实际上JS的编译器只会对两个关键字进行预处理————var和function
二、编译器眼中的天龙人var和function
那么在JavaScript对var和function进行预解析的时候,到底都做了些什么呢? 首先JavaScript会创建一个全局对象GO,一般可以将其认为为window,这个全局对象只会收集两种东西:
-
一种是定义在全局的var变量和函数,另一种是在函数调用中未声明的变量
// var a = undfined
function b(){
a = 10; //使用未定义的变量,在执行时会在GO里定义一个同名变量
}
b();
值得注意的是,GO中将a赋值成了undefined,这并不是一个个例,JS在解析代码时,会从上到下走一遍,把所有的var声明的变量找到,记录在GO中,并将其赋值为undefined,同时也会把所有的使用function声明的函数记录在GO中,并将该函数对应的函数体赋给该函数声明
// console.log(a) //undefined
var a = 1;
function b(){} //在GO中会被初始化为{a:undefined,b:function b(){}}
在此之后,才会真正的执行代码,就如上图的代码中a在预编译过程中先被赋值为undefined,然后执行代码,当执行到var a = 1的时候,a的值才会真的变为1。
函数的定义在编译时,被预编译过,因此会直接跳过,直到函数被执行时,才会继续进行编译。在编译的过程中,每个函数会产生一个类似于GO的活动对象AO,AO与GO类似,但是它的范围只局限在某个函数之内,随着函数产生而产生。如果一个变量在函数的AO里有,同时GO里也有,那么会先找AO里的,没有才会向上级找,直到找到GO为止。
var a = 10;
function b(){
var a = 100;
console.log(a); // 100,这里不会是10
}
我们刚刚讨论了AO里没有变量的情况,那么AO里有很多同名变量的情况是什么样的呢?
function a(b){
console.log(b);
var b = 10;
function b(){};
console.log(b);
}
a(1);
在这种情况中,function a的AO中只会定义一次b,之后定义的b的值会覆盖掉之前定义的值。那么这是以一种什么样的顺序覆盖的呢?
牢记这个顺序 函数>参数>var变量!!
也就是说同一个AO下,先找到var b,然后将其赋值为undeifined,之后找到参数b,因为传入的参数为1,所以此时b=1,最后找到函数声明b,于是AO中的b就被赋值成了一个函数。因此,当做完预编译之后,开始执行代码时,第一个console.log(b)会打印出function b(){},之后执行var b = 10;,此时AO中的b为10,function b已经预编译过了,就不会再次执行,因此,第二个console.log(b)会打印出10。
三、观察并思考
回到文章开始那道题,根据上面的内容。在函数执行时,先找到var和function,这里会遇到第一个难点,预编译的时候会不会考虑if,答案是不会考虑!预编译时,JS的引擎是不会考虑到if是否会实际执行,它只会把所有的var和function忠实的记录下来,但是按照之前的理论当记录到function时,JS会将函数名记录下来,并为其赋上函数体,那为什么这里是undefined呢?
关于这一点,我并没有找到相关的资料。只能根据现有的情况分析一下。在上题中,if(true)和if(false)的返回结果是一样的,说明此时JS引擎并没有解析if里的内容。推测可能为JS引擎遇到需要判断的条件时,会把函数按undefined返回。
function m() {
console.log(i);
var i = 2;
while(i<0){ // 这里也是个条件判断语句
function i() {
}
}
}
m(); // 执行完i的值为undiefined
因此,我们可以简单的认为,当function定义在条件判断语句中时,JS的预解析会将其赋值为undefined,而不是function