每日一学:JS中的预编译

94 阅读5分钟

前言

我是Ace,在juejin.cn/post/737244… 一文中我说明了在JS中作用域的概念,并没有详细说说预编译的概念,但在JS中这也是很重要的,对于JS的学习及理解也有着重要的作用,废话不多说,直接开始吧。

预编译

声明提升

首先我们来看这一份代码

var a = 1
console.log(a);

这是一份很简单的代码,它的结果也很简单

image.png

那么再看看这一份代码

console.log(a);
var a = 1

这又会输出什么呢?有人会说,这不是会报错吗,没错,这在C中没错,但在JS就不一样了,我们来看看结果:

image.png

undefined?未被定义?这不应该报错吗?所以这就要扯到今天的第一个主题了:声明提升

在绝大多数语言的代码执行过程中,编译总是发生在代码执行的前一刻。在JS中,变量的声明就被分为了两部分:声明然后赋值。让我们来看下执行的流程吧:

image.png

从执行过程中,我们可以看出,‘秘书’首先是看到了输出log,但它并不能执行,然后找到了全局的变量a,这里它就可以进行变量声明,但不会执行后面的= 1,所以这时它的值就是undefined,也就是“有这个东西,但没有值”的概念,然后‘秘书’就将这份‘笔记’交给了‘董事长’,它执行时,肯定是先执行console.log(a)对吧,于是就输出了这时a的值undefined,然后就给a赋值为1。

这就是变量的声明提升,还有函数在执行时也会声明提升。对于函数声明,整个函数定义(包括函数名、参数列表和函数体)都会被提升。这意味着可以在函数声明之前调用该函数。

sayHello(); // 正常输出 "Hello, world!"
function sayHello() {
    console.log("Hello, world!");
}

在这个例子中,尽管sayHello()函数调用在函数声明之前,但由于函数声明被提升到了作用域的顶部,因此函数调用可以正常执行。与变量的声明提升类似。

这就是声明提升,简单总结一下

  1. 变量声明,声明提升
  2. 函数声明,整体提升

下面来进入主题预编译

预编译

在JS中,主要分为了全局作用域以及函数作用域,而预编译在不同的作用域也有所不同,首先,我们来看看函数的预编译,它的主要规则如下:

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

2. 找形参和变量声明,将形参和变量名作为AO的属性,值为 undefined

3. 将实参和形参统一

4. 在函数体内找函数声明,将函数名作为AO的属性名,值赋予函数体 然后我们来k看这一份代码:

function fn(a){
    console.log(a);
    var a = 123
    console.log(a);
    function a(){}//函数声明
    console.log(a);
    var b = function(){}//函数表达式
    console.log(b);
    function d(){}
    var d = a
    console.log(d);
}
fn(1)

我看的都头大,不知你们感受如何?但其实根据我给出的规则,可以轻松的理透这其中规律。

首先,我们创建AO

AO:{
}

然后找形参和变量声明,将形参和变量名作为AO的属性,值为 undefined,这里函数的形参不是只有函数声明里的a,变量有a、函数表达式bd,然后写入AO:

AO:{
a:undefined
b:undefined
d:undefined
}

然后将形参和实参统一,我们在fn的调用时给a传入了一个值1,这里我们需要统一:

AO:{
a: undefined -> 1
b: undefined
d: undefined
}

最后在函数体内找函数声明,将函数名作为AO的属性名,值赋予函数体,而代码中有function a(){}function d(){}这俩函数声明。于是就有了:

AO:{
a: undefined -> 1 -> function(){}
b: undefined
d: undefined -> function(){}
}

然后函数的预编译就完了,开始执行。开始,是输出a对吧,这是a的值为函数体,于是输出函数体;然后给a赋值为123,此时a的值为123,然后又输出a,于是输出123,然后跳过函数声明,又输出a123;然后给b赋值为函数体,此时b的值为函数体,然后又输出b,于是输出函数体;然后给d赋值为a的值,此时a的值为123,于是给d赋值为123,然后输出d,于是输出123,到此结束,我们就得到了一份完整的AO以及输出结果:

AO:{
    a : undefined -> 1 -> function a(){} -> 123,
    b : undefined -> function (){},
    d : undefined -> function d(){} -> 123 
}

输出:
函数体
123
123
函数体
123

我们来验证一下结果:

image.png

没有任何问题,然后来全局预编译,它的规则如下:

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

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

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

与函数预编译其实并无不同,只是少了形参的声明以及同步,但全局又怎么会有形参呢,所以我就不多赘述了。

结语

总而言之,预编译与声明提升是JavaScript语言特性的重要组成部分,理解它们不仅能够帮助我们避免常见的编程陷阱,还能够促使我们以更加严谨和高效的方式进行开发。在这个快速变化的技术领域,保持好奇心,持续探索和实践,是每一位开发者成长道路上不可或缺的部分。好了,我是Ace,我们下次分享再见!!!