深入浅出 JS 预编译

102 阅读4分钟

前言

在 JavaScript 编程的世界里,理解代码的执行机制至关重要。今天,我们就来揭开 JS 引擎执行代码时的神秘面纱,深入了解预编译过程,让你对代码的运行有更清晰的认识。

执行上下文:代码运行的环境

每当 JS 引擎执行一段代码,就会创建一个执行上下文。它是代码运行时的环境,包含了运行所需的所有信息。执行上下文的生命周期分为创建阶段和执行阶段。

创建阶段

在这个阶段,JS 引擎主要完成三件事:

    1. 创建变量对象。对于 var 声明的变量和函数声明,它们会被提前创建,变量初始值为 undefined,而函数声明会将函数体赋值给函数名。
    1. 确定 this 指向。根据执行上下文的不同,this 的指向也会有所不同。
    1. 确定作用域。作用域决定了变量的可见性和生命周期。

执行阶段

进入执行阶段后,引擎开始为变量赋值,调用函数,并顺序执行其他代码。这个过程就是我们通常理解的代码运行过程。

预编译:执行前的准备

预编译是 JS 引擎执行代码前的关键步骤。它的主要任务是处理变量和函数的声明提升,为代码的顺利执行做好准备。

全局编译过程

当代码在全局作用域执行时,JS 引擎会:

    1. 创建全局执行上下文对象。
    1. 找到所有变量声明,将变量名作为上下文的属性名,初始值设为 undefined
    1. 找到所有函数声明,将函数名作为上下文对象的属性名,并将函数体赋值。
    1. 最后执行函数体。

函数体编译过程

当函数被调用时,JS 引擎会为函数创建一个新的执行上下文:

    1. 创建函数的执行上下文对象。
    1. 找到函数的形参和变量声明,将它们作为上下文的属性名,初始值设为 undefined
    1. 将实参值赋给对应的形参。
    1. 在函数体内寻找函数声明,将函数名和函数体添加到上下文对象中。
    1. 执行函数体。

代码示例:预编译的实际效果

下面通过一个代码示例来直观感受预编译的影响:

a10;
console.log(a); // 输出 10
var b20;
function foo() {
  console.log(b); // 输出 20
  console.log(a); // 输出 undefined
  var a30;
  console.log(a); // 输出 30
}
var a;
foo();

在这段代码中:

    1. 全局执行上下文在编译阶段创建了变量 a 和 b,以及函数 foo。此时 a 和 b 的值为 undefined
    1. 在执行阶段,a 被赋值为 10,b 被赋值为 20。
    1. 当调用 foo() 时,函数执行上下文被创建。在编译阶段,函数内的变量 a 被创建,初始值为 undefined
    1. 函数执行时,首先输出 b 的值 20(来自全局作用域),然后输出 a 的值 undefined(因为函数内的 a 还未被赋值)。
    1. 接着,a 被赋值为 30,并输出 30。

最终,代码的输出结果为:1020undefined30

变量提升:预编译的直接结果

预编译过程导致了变量提升现象。对于 var 声明的变量,它们会被“提升”到作用域的顶部,但初始值为 undefined。这意味着我们可以在变量声明之前访问它,不过此时变量的值是 undefined

需要注意的是,let 和 const 声明的变量不会被提升,如果在声明前访问它们,会导致报错。这就是所谓的暂时性死区(TDZ)。

总结:深化理解,提升编码能力

理解 JS 的预编译过程有助于我们更好地把握代码的执行流程,避免因变量提升等特性引发的潜在问题。通过深入学习执行上下文和预编译机制,我们能够写出更高效、更可靠的 JavaScript 代码。

 

图片