JavaScript攻略:预编译
前言
在JavaScript中,代码的执行并不是简单的从上到下顺序执行,而是在执行之前会经历一个预编译的过程。本文将通过一些具体的代码示例来深入解析预编译过程。当然在了解预编译之前一定要对JavaScript中一些知识点有所了解,这些前置知识点能帮助你更好地了解预编译。
前置知识点
执行上下文
执行上下文是代码执行时的一个环境,它包含了执行代码所需的所有信息。每个执行上下文都有它自己的作用域链,用于确定如何访问变量和函数。在JavaScript中,主要有两种类型的执行上下文:全局执行上下文(GO)和函数执行上下文(AO)。
AO(Activation Object)
函数执行时的一个内部对象,它存储了函数内部的所有局部变量和函数参数
GO(Global Object)
全局执行上下文中的一个特殊对象,它代表了全局作用域。在浏览器环境中,Global Object通常是window
对象,而在Node.js等服务器端JavaScript环境中,它则是global
对象。
变量和函数声明提升
在JavaScript中,变量和函数的声明会被提升到其作用域的顶部,这一过程称为提升
。
变量声明,声明提升
在作者前一篇有关作用域的文章中提到过:var 存在声明提升的特性,也就是说,无论 var 声明在函数的哪个位置,它都会被处理成好像是在函数的最顶部声明的一样。
注意:只有声明会被提升,赋值操作不会哦。
函数声明,整体提升
与var
声明的变量不同,函数声明不仅仅是声明被提升,整个函数体也会被提升到其作用域的顶部。这意味着,在函数声明之前就可以调用该函数。
test() //函数声明会整体提升
function test(){
var a=123
console.log(a);
}
注意:函数会首先被提升,然后才是变量哦。
作用域与作用域链
作用域是执行期上下文对象的集合,这种集合呈链式连接,我们把这种链状关系称之为作用域链。作用域链决定了变量和函数在当前执行上下文中如何被访问和解析。每个执行上下文都有它自己的作用域,而作用域链则是一个由多个作用域组成的链式结构,用于在当前的执行上下文中查找变量或函数。如果在当前作用域中找不到某个变量或函数,JavaScript引擎就会沿着作用域链向上查找,直到找到为止或达到全局作用域。
预编译
预编译与编译有什么区别吗?
作为一个刚入门的JavaScript菜鸟,我确实会有所疑问,这两个都是都是编译,有什么区别啊?虽然都有编译两个字,但是这两个是完全不同的东西,它们共同构成了JavaScript代码从文本到可执行指令的转换路径。
预编译
是编译前的一个准备阶段。在这个过程中,JavaScript引擎会分析代码中的变量和函数声明,但不会执行任何实际的代码逻辑。预编译的目的是为了确定代码在执行时的环境和状态,以便后续的代码执行能够顺利进行。
编译
是JavaScript代码从文本转换为可执行代码的过程,这个过程中主要有三个步骤:词法分析、语法分析、代码生成。如果想要更详细的了解编译原理这方面的知识,可以看作者的前一篇有关作用域的文章。
了解以上知识后,接下来我们将通过一系列具体的代码示例来详细解析JavaScript的预编译过程,包括全局预编译和函数预编译。
全局预编译
预编译发生在全局,在JavaScript代码开始执行之前,JavaScript引擎会进行以下步骤:
- 创建全局对象(GO)
- 查找变量声明:查找全局作用域中变量,并将这些变量名作为属性添加到GO上,且值被初始化为
undefined
。 - 查找函数声明:查找所有的函数声明(使用
function
关键字定义的函数,而不是函数表达式)。这些函数声明的整个函数体(包括函数名)会被添加到GO上,作为属性,其值是函数体本身。这意味着函数声明会被“提升”到全局作用域的顶部。
函数预编译
函数预编译发生在函数调用之前,为该函数创建一个新的执行上下文,并进行以下步骤:
- 创建活动对象(AO)
- 查找形参和变量声明:查找函数内所有形参和变量声明,并将形参和变量名作为AO的属性名,值为undefined。
- 形参和实参统一:如果有实参传递给函数,这些实参的值会被依次赋给对应的形参。
- 查找函数声明:在函数体内,所有的函数声明(使用
function
关键字定义的函数)也会被添加到AO上,作为属性,其值是函数体本身。这些函数声明也会被提升,但它们只在函数体内可见。
预编译过程示例
function fn(a){
console.log(a); // [Function: a]
var a=123
console.log(a); // 123
function a(){}
console.log(a); // 123
var b=function (){}
console.log(b); // [Function: b]
function c(){}
var c = a
console.log(c); // 123
}
fn(1)
全局预编译:只有一个函数fn
GO:{
fn: function(){}
}
函数预编译:第13行函数fn被调用,JavaScript为函数fn的执行创建一个新的作用域,即活动对象(AO)
- 参数:函数fn 有一个参数 a。在预编译阶段,这个参数会被添加到AO中,并初始化为undefined。当函数被调用时,参数 a 会被赋予传入的实参值 1。
- 变量声明:在函数体内,有两个变量声明
var a=123;
和var b = function(){}
,以及一个变量赋值var c = a;
。由于变量声明会被提升, a、b 和 c 都会在预编译阶段被添加到AO中,并初始化为 undefined。在执行阶段,var a = 123;
声明会覆盖参数 a。 - 函数声明:在函数体内,有两个函数声明
function a(){}
和function c(){}
。由于函数声明提升,这两个函数会在预编译阶段被添加到AO中。注意:尽管有函数声明function a(){}
,但它不会影响参数a或变量a(var a = 123;),函数声明function a(){}在AO中存在,但在执行阶段不会被作为函数执行。
AO:{
a:undefined --> 1 --> function(){} <--预编译 执行阶段--> 123
b:undefined --> function(){}
c:undefined --> function c(){}
}
这段代码的执行结果我已经写在注释中了。
总结
通过认真阅读本篇文章,相信大家对于JavaScript的预编译过程有了更好地了解,如果在你阅读的过程中有发现本篇文章的疏漏之处,欢迎评论区指正。
转载自:https://juejin.cn/post/7394788843206508595