浅浅认识一下js中的预编译

288 阅读5分钟

声明提升

变量和函数声明的提升(Hoisting):

  1. 变量声明:使用var声明的变量会被提升到当前作用域的顶部,但变量的初始化(赋值)保留在原地。这意味着在变量声明语句执行前,变量已经存在但其值为undefined。

  2. 函数声明:使用function关键字声明的函数也会被提升到作用域的顶部,这意味着在整个作用域中,函数可以在声明之前被调用。

函数中的预编译

编译器的操作步骤:

  1. 创建函数的执行上下文对象(AO对象 Activation Object)

  2. 会找形参和变量声明,再将形参和变量名作为AO的属性,值为undefined。变量声明(var)会被提升至作用域顶部,但变量初始化不会。

  3. 将实参和形参统一

  4. 在函数体内找函数声明,将函数名作为AO的属性名,值被赋予给函数体。如果一个函数和一个变量名冲突,函数声明会覆盖变量声明。 例如:

    function fn(a) {
        console.log(a);
        var a = 123;
        console.log(a);
        function a() {} // 函数声明,声明一个叫做a的函数体
        console.log(a);
        var b = function(){} // 函数表达式
        console.log(b)
        function d(){}
        var d = a;
        console.log(d);
    }
    fn(1);
    
    1. 执行前编译器创建一个AO对象
    2. 在函数体里面找形参和变量声明,将它们作为ao的属性名,值为undefined AO: { a: undefined, b: undefined, d: undefined, }
    3. 将实参和形参统一 AO: { a: undefined, -->1 b: undefined, d: undefined, }
    4. 在函数体内找函数声明,将函数名作为AO的属性名,值被赋予给函数体 AO: { a: 1, --> function a(){} b: undefined, d: undefined, --> function d(){} }
    5. 执行代码
  •     第二行的console.log(a)的输出是[Function: a]
    
  •     第三行将123赋值给a
    
  •     第四行console.log(a)的输出是 123
    
  •     第五行是函数声明,不用管
    
  •     第六行console.log(a)的输出是 123
    
  •     第七行先声明了一个变量b,再将匿名函数赋值给变量b
    
  •     第八行的console.log(b)输出的是[Function: b]
    
  •     第九行是函数声明,不用管
    
  •     第十行是将a的值赋值给d,此时a的值为123,所以此时d的值为123
    
  •     第十一行的console.log(d)输出的是123
    
    最终的输出为:
        123
        123
        [Function: b]
        123

全局的预编译

1. 创建全局执行上下文对象(GO)

2. 找变量声明,变量名作为GO的属性名,值为undefined

3. 在全局找函数声明,函数名作为GO的属性名,值为函数体Function

例如:

```var global = 100
function fn(){
    console.log(global);
}
fn();
```
  1. 创建全局执行上下文对象(GO)

  2. 找变量声明,变量名作为GO的属性名,值为undefined

  3. 在全局找函数声明,函数名作为GO的属性名,值为函数体Function

    GO: {
            global: undefined 
            fn: function fn() {}
        }
    

4. 执行代码

    此时
    ```
    AO: {
            // 无变量声明和函数声明
      }
    ```

当在一个函数内部访问一个变量时,它会首先在当前函数的作用域(AO)中查找该变量。如果在当前作用域找不到,就会向上一层作用域查找,这个过程一直持续到全局作用域。在这个例子中,fn 函数内部没有声明自己的 global 变量,因此它会向上访问全局作用域中的 global 变量,值为 100。

调用栈

调用栈(Call Stack),也常被称为函数调用栈,是编程语言中一种核心的执行模型概念,用于追踪和管理函数的调用流程。在JavaScript中,这一概念尤为重要,因为JavaScript是基于函数调用和执行上下文管理的。

具体来说,调用栈是一种数据结构,通常实现为栈(Last-In-First-Out, LIFO)的形式,它记录了函数调用的顺序和状态。当一个JavaScript函数被调用时,会做以下几件事情:

1.创建执行上下文:引擎会为该函数创建一个新的执行上下文,这个上下文包含了函数执行所需的所有信息,比如局部变量、参数、this的值以及对外部变量的引用。

2.压栈:新创建的执行上下文会被推(Push)到调用栈的顶部。这意味着每次函数调用都会在栈顶添加一个新的层次,代表当前正在执行的函数。

3.执行函数:函数开始执行其代码块。

4.弹栈:当函数执行完毕并返回时,其对应的执行上下文会从调用栈中弹出(Pop),控制权返回到调用该函数的上一层函数或者全局作用域。

如果函数内部又调用了其他函数,则这个过程会递归进行,形成一个函数调用的层级结构。调用栈能够帮助我们理解和追踪代码的执行流程,特别是在调试时,通过查看调用栈可以了解函数是如何被一步步调用的,以及当前执行到哪一层级。

当调用栈过深,即调用的函数太多以至于超过了引擎允许的最大调用栈大小时,会抛出“栈溢出”(Stack Overflow)错误。

例如:

function add(){
    var b = 10;
    return a+b;
}
add();

image.png

感谢阅读!