本文详细解析JavaScript引擎在代码执行前的预编译过程,区分脚本级与函数级的预编译差异。通过GO(全局对象)与AO(活动对象)的创建步骤,阐明变量声明提升与函数声明提升的具体表现,并结合实例代码展示执行流程中的值变化。
JavaScript运行三部曲
通常认为JavaScript属于解释型语言,执行时逐行解析并运行,但这是否意味着不存在预编译阶段?在脚本实际运行前,JavaScript引擎究竟执行了哪些准备工作?接下来我们将深入探讨这一过程。
1-JavaScript运行三部曲
语法分析
预编译
解释执行
语法分析主要检查代码是否存在基础语法错误;解释执行则是真正运行代码的过程;而预编译可以简单理解为在内存中分配空间,用于存储变量和函数定义。
JS预编译时机
2-JS预编译什么时候发生
预编译具体在何时触发?许多人误以为它仅发生在script标签内代码执行之前。虽然这没错,预编译确实会在script代码执行前发生,但它更频繁地出现在函数执行之前。
实例分析与执行流程
3-实例分析
首先需要明确两个核心概念的区别:变量声明(使用var关键字)与函数声明(function语句)。
<script>
var a = 1;
console.log(a);
function test(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
var c = function (){
console.log("I at C function");
}
console.log(c);
test(2);
</script>
分析过程如下:
页面加载时,系统会创建GO全局对象(Global Object),即window对象;
随后加载第一个脚本文件;
脚本加载完成后,引擎会检查语法是否合法;
接着开始预编译:查找变量声明,将其作为GO的属性,初始值设为undefined;查找函数声明,同样作为GO的属性,值赋予完整的函数体;
预编译
//抽象描述
GO/window = {
a: undefined,
c: undefined,
test: function(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
}
最后进入解释执行阶段,直到遇到调用函数test(2)的语句;
//抽象描述
GO/window = {
a: 1,
c: function (){
console.log("I at C function");
}
test: function(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
}
在调用函数test()之前,会先发生预编译过程: 创建AO活动对象(Active Object);
查找形参和变量声明,将其值设为undefined;
将实参的值赋给对应的形参;
查找函数声明,将函数体赋值给函数名属性;
预编译之前面1、2两小步如下:
//抽象描述
AO = {
a:undefined,
b:undefined,
}
预编译的第三步如下:
//抽象描述
AO = {
a:2,
b:undefined,
}
预编译的第四步如下:
//抽象描述
AO = {
a:function a() {},
b:undefined
d:function d() {}
}
执行test()函数时的过程变化如下:
//抽象描述
AO = {
a:function a() {},
b:undefined
d:function d() {}
}
--->
AO = {
a:123,
b:undefined
d:function d() {}
}
--->
AO = {
a:123,
b:function() {}
d:function d() {}
}
最终执行结果:

注意:
在预编译阶段,系统只处理变量声明和函数声明,不涉及初始化(赋值)操作,且匿名函数不参与预编译;只有在解释执行阶段,才会进行变量的实际初始化。
预编译规则总结
预编译(函数执行前)
创建AO对象(Active Object)
查找函数形参及函数内部的变量声明,将形参名及变量名作为AO对象的属性,初始值设为undefined
实参与形参统一,将实参的值赋给形参
查找函数声明,将函数名作为AO对象的属性,值设为函数引用
预编译(脚本代码块script执行前) 查找全局变量声明(包括隐式全局变量,即省略var的声明),将变量名作为全局对象的属性,初始值设为undefined
查找函数声明,将函数名作为全局对象的属性,值设为函数引用
预编译小结
预编译两个小规则
函数声明整体提升-(具体点说,无论函数调用和声明的位置是前是后,系统总会把函数声明移到调用前面)
变量 声明提升-(具体点说,无论变量调用和声明的位置是前是后,系统总会把声明移到调用前,注意仅仅只是声明,所以值是undefined)
预编译前奏
imply global 即任何变量,如果未经声明就赋值,则此变量就位全局变量所有。(全局域就是Window)
一切声明的全局变量,全是window的属性; var a = 12;等同于Window.a = 12;
函数预编译发生在函数执行前一刻。