likes
comments
collection
share

JavaScript攻略:预编译

作者站长头像
站长
· 阅读数 20

前言

在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引擎会进行以下步骤:

  1. 创建全局对象(GO)
  2. 查找变量声明:查找全局作用域中变量,并将这些变量名作为属性添加到GO上,且值被初始化为undefined
  3. 查找函数声明:查找所有的函数声明(使用function关键字定义的函数,而不是函数表达式)。这些函数声明的整个函数体(包括函数名)会被添加到GO上,作为属性,其值是函数体本身。这意味着函数声明会被“提升”到全局作用域的顶部。

函数预编译

函数预编译发生在函数调用之前,为该函数创建一个新的执行上下文,并进行以下步骤:

  1. 创建活动对象(AO)
  2. 查找形参和变量声明:查找函数内所有形参和变量声明,并将形参和变量名作为AO的属性名,值为undefined。
  3. 形参和实参统一:如果有实参传递给函数,这些实参的值会被依次赋给对应的形参。
  4. 查找函数声明:在函数体内,所有的函数声明(使用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)

  1. 参数:函数fn 有一个参数 a。在预编译阶段,这个参数会被添加到AO中,并初始化为undefined。当函数被调用时,参数 a 会被赋予传入的实参值 1。
  2. 变量声明:在函数体内,有两个变量声明 var a=123;var b = function(){} ,以及一个变量赋值 var c = a;。由于变量声明会被提升, a、b 和 c 都会在预编译阶段被添加到AO中,并初始化为 undefined。在执行阶段,var a = 123; 声明会覆盖参数 a。
  3. 函数声明:在函数体内,有两个函数声明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
评论
请登录