引言
JavaScript中的预编译是指在代码执行阶段之前,JavaScript引擎会对代码进行解析和准备工作的过程。虽然JavaScript没有像传统编译语言(如C++或Java)那样有明确的编译过程,但是在代码执行之前,JavaScript引擎会执行一些步骤,以确保代码可以正确执行。
预编译阶段涉及以下几个关键步骤:
-
变量提升(Hoisting) :在预编译阶段,变量和函数声明会被提升到当前作用域的顶部。这意味着可以在声明之前使用变量和函数,但是如果在声明之前访问变量的值,它会是
undefined
。javascriptconsole.log(a); // undefined var a = 5;
在预编译阶段,上面的代码实际上被处理为:
javascriptvar a; // 变量声明被提升 console.log(a); // undefined a = 5;
-
函数提升:与变量提升类似,函数声明也会在预编译阶段被提升到当前作用域的顶部。
jstest(); function test(){ var a = 2; console.log(a);//a = 2 }
在预编译阶段,上面的代码会函数的声明整体提升,所以上面的代码的结果才会是2。
js//函数提升 function test(){ var a = 2; console.log(a); } test();
-
作用域的建立:在预编译阶段,JavaScript引擎会确定每个变量和函数的作用域,以及它们的作用域链。
-
this的绑定:在预编译阶段,this的绑定会被确定,但实际的this值是在函数执行时确定的。
总之,JavaScript的预编译阶段用于解析和准备代码,使得代码能够按照预期执行。理解预编译阶段对于理解JavaScript中变量作用域、作用域链以及函数调用的行为至关重要。
预编译
代码在执行前需要进行编译操作,用于确定代码之间的各种关联。 在JavaScript中,GO(Global Object)和AO(Activation Object)是两种执行上下文相关的对象,它们在代码执行过程中起着重要的作用。
GO(Global Object):全局对象包含了JavaScript中的全局变量和函数。当代码在全局作用域中执行时,所有的全局变量和函数都会成为全局对象的属性。
AO(Activation Object):活动对象是在函数执行上下文中创建的对象,用于存储函数的局部变量、参数和内部函数的引用。
那么JavaScript引擎在预编译会进行哪些步骤呢,这里总结了两种情况,当预编译发生在全局的时候以及发生在函数体内的时候。
- 发生在全局
- 创建GO对象
- 找变量声明,将变量名作为GO的属性名,值为undefined
- 在全局找函数声明,将函数名作为GO的属性名,值为该函数体
- 发生在函数体内
- 创建一个AO对象
- 找形参和变量声明,将形参和变量名作为AO的属性名,值为undefined
- 形参和实参统一
- 在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体
例1
知道了上面的步骤那我们先用下面的例子来具体看看代码的预编译如何执行的
js
var a = 1;
function fn(a){
var a = 2;
function a(){}
console.log(a);// 2
}
fn(3);
第一步:首先创建一个GO对象GO:{ },在全局中找变量声明,找到了a并赋值undefined,在全局找函数声明,找到fn作为属性名,值为函数体,也就是如下所示
GO对象
GO:{
a:undefined,
fn:function (a){
var a = 2;
function a(){}
console.log(a);
}
}
在预编译之后就会执行代码,其中声明的变量a会被赋值为1,a的值就会从undefined变为1,也就是GO对象里的a:undefined
会变成a:1
。在GO对象里只有这一个变量被赋值,GO对象里面的函数体fn因为还没有被调用,所以并不会执行。
第二步:首先创建一个AO对象AO{ },找形参和变量声明,找到形参a并赋值为undefined,找到变量a也赋值为undefined,但是因为对象中属性名key是唯一的,所以找到的形参a和变量a是同一个属性名,值为undefined。后面再进行形参和实参统一,a被赋值为3,最后在函数体内找到函数声明a,值为函数体。如下所示
AO对象
//根据步骤AO对象在不断的被赋值,在预编译的最后a的值为函数体
AO:{
a: undefined--->undefined--->3--->function(){}
}
//预编译最后的AO对象
AO:{
a: function(){}
}
最后进行函数的执行,执行代码var a = 2
,a被赋值为2,所以代码执行的结果是2。
例2
再来一段代码,你能知道它们打印出来的结果是什么吗
js
function fn(a){
console.log(a);//function
var a = 123;
console.log(a);//123
function a(){}
console.log(a);//123
var b = function(){};
console.log(b);//function
function c(){}
var c = a;
console.log(c);//123
}
fn(1);
第一步:创建GO对象,找声明变量,发现这段代码中在全局并没有变量的声明,接着找函数的声明,找到fn,值为函数体,如下所示
GO对象
GO:{
fn: function(){}//这里简写了函数体
}
全局执行并没有改变,函数体没有调用不会执行
第二步:创建AO对象,找形参和变量声明并赋值undefined,找到形参a赋值为undefined,变量a还是赋值为undeined,变量b赋值为undefined,变量c赋值为undefined。形参和实参统一,a赋值为1。在函数体内找函数声明并赋值为函数体,a赋值为函数体,c赋值为函数体。如下所示
AO对象
//AO对象中属性名一样的会被不断的赋值,最后a的值为函数体,c的值为函数体
AO:{
a:undefined--->undefined--->1--->function(){},
b:undefined,
c:undefined--->function(){}
}
//预编译最后AO对象的结果
AO:{
a:function(){},
b:unfined,
c:function(){}
}
最后进行函数的执行,第一个a打印出来是[Function: a]
,执行代码var a = 123
,a被赋值为123,第二个a打印出来是123
,函数a并没有被调用不执行,第三个a打印出来还是123
,执行代码var b = function(){}
,b被赋值为函数体,所以b的打印结果为[Function: b]
,函数c没有被调用不执行,执行代码var c = a
,c被赋值为a,上一个a的值为123,所以c的打印结果为123
。

总结
看到这里有没有对JS代码的执行有了更详细的了解呢,最后我再小小的总结一下,代码是先编译后执行。 步骤是先全局代码预编译,全局代码执行,执行到函数的调用,才会开始函数体内的预编译,再进行函数的执行,直到所有的代码执行完。
