前言
JavaScript中存在预编译的过程,是为了确保代码在执行之前具有正确的执行环境和作用域。预编译阶段会对变量和函数进行声明和初始化,并建立作用域链,以便在代码执行阶段可以正确地访问和处理这些变量和函数。通过预编译阶段,JavaScript的代码在执行之前可以进行必要的准备工作,以确保变量和函数的正确性和可用性。这有助于提高代码的可靠性和执行效率。
首先我们来做一道题目,请问下面的这段代码会输出什么?
function a(a) {
console.log(a);
var a=456
}
a(1)
答案是1。我们再来看一道复杂一点的题目
var a;
function test(x) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
}
console.log(a);
a=10
console.log(a);
test(1)
代码执行结果为
undefined
10
[Function: a]
123
123
[Function: b]
要想知道为什么,就要知道什么是预编译。接下来我给大家写一下这段代码的执行步骤。
两种预编译
首先,预编译分为全局预编译和函数体预编译。全局预编译发生在页面加载完成时执行,而函数预编译发生在函数执行的前一刻。
全局预编译可分为三步:
1.创建GO对象(全称为Global Object)。
2.找变量声明,赋值为undefined
3.找函数声明,赋值为函数体
函数体预编译可分为四步:
1.创建AO对象(全称为Activation Object)
2.找形参和变量声明,赋值为undefined
3.将实参赋值给形参
4.找函数声明,赋值为函数体
知道步骤之后,我们再来分析上面这道题目。
分析步骤
根据步骤,我们首先创建一个GO对象
GO{
}
然后找变量声明,赋值为undefined,并将变量作为GO的属性
GO{
a:undefined
}
再找函数声明,赋值为函数体,同样作为GO的属性(注意,如果此时变量名已经存在且与函数名同名,则GO中已存在的同名属性会被赋值成函数体)
GO{
a:undefined,
test:function(){}//此处简写
}
最后依次执行代码,所以第一个console.log(a)打印为undefined,之后执行a=10,第二个console.log(a);就打印的是10。
当执行到Test(a)时就开始函数体预编译了。
按照步骤,我们首先创建AO对象
AO{
x:undefined,
a:undefined
}
然后将实参赋值给形参
AO{
x:1,
a:undefined
}
再找函数声明
AO{
x:1,
a:function(){},//如上面所说,函数名与变量名相同则被覆盖
b:function(){}
}
最后依次执行代码,函数体内第一个console.log(a)打印为[Function: a],然后执行a=123,第二个和第三个console.log(a)打印都为123,最后一个打印为[Function: b]。
以上就是这一道题目的解题步骤,下面我们可以通过几道题目来巩固一下。
第一道
function test() {
console.log(b);//undefined
if (a) {
var b = 100;
}
console.log(b);//undefined
c = 234;
console.log(c);//234
}
var a;
test();
a = 10;
console.log(c);//234
第二道
var a = 10;
function foo() {
console.log(a);//undefined
if (true) {
var a = 20;
}
}
foo();
第三道
var x = 10;
function outer() {
return function inner() {
console.log(x);//20
}
}
var innerFunc = outer();
x = 20;
innerFunc();
感谢您的观看,不足之处请指正!