JavaScript执行分为三个步骤:首先进行语法分析,看看代码是不是符合JS语法规范,然后要进行预编译,最后才开始解释执行;这个预编译的环节发生在函数执行的前一刻。
可以看到,在下面这个函数中,形参名、变量名、函数名很多都一样。那么每次console.log()出来的东西都是什么?只要了解了预编译,就会很清晰的知道。
function precompile(a){
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function () {}
console.log(b);
function c() {}
}
precompile(1);
在预编译过程中,首先根据这个名叫precompile的函数,创建了一个AO对象,也叫执行期上下文。然后开始在AO对象中赋值。这个过程按顺序又分为三个步骤:
1.找形参和变量声明,将变量和形参名作为AO属性名,默认值为undefined。
我们这个函数中,形参有一个,变量声明有两个,但因为形参名是a,其中一个变量声明也是a,所以AO对象中只能有一个a。
AO{
a:undefined;
b:undefined;
}
2.将实参值和形参统一
实参为1,所以根据规则AO中的a也应为1。
AO{
a:1;
b:undefined;
}
3.在函数体里面找函数声明,值赋予函数体
这里有函数a和c两个函数声明,所以a属性不需要重复添加,但是原来a的属性值1被覆盖为函数体。c属性因为没有出现过,所以要添加一个,属性值就是函数体。
AO{
a:function a(){};
b:undefined;
c:function c(){};
}
到这里,预编译就完成了,然后根据预编译的结果开始执行函数,所以第一个打印的a的结果是function a(){},第二个打印a的结果是123,因为赋值了。第三个打印a的结果也是123。因为函数a声明已经被提升上去了。然后虽然AO中的b是undefined,但是又进行了赋值,所以console.log(b);是function () {}
预编译不只发生在函数中,全局中也会发生这一环节。比如:
console.log(a);
var a = 123;
这时我们console出来的a是undefined;那我们再在下面加上function a(){}
console.log(a);
var a = 123;
function a(){}
这时我们console出来的a会是function a(){};这就再次验证了预编译中的规则。