什么是js的预编译?深入了解声明提升

183 阅读4分钟
前言

在之前的学习中,我们知道了什么是js中的作用域和什么是声明提升 (什么是作用域 - 掘金 (juejin.cn)),今天带大家深入了解声明提升的底层原理。

声明提升

首先简单的说一下什么是声明提升,这里主要讲两个,一个是var声明的函数变量存在声明提升

console.log(a)//此时输出undefined
var a=1

其次是函数声明整体提升(函数名和函数体都提升)

foo()
function foo(){
    console.log('hello');
}

很奇怪,为什么一个变量没有声明就输出却不会报错而是undefined?函数没有先声明就调用却能正常执行。今天就来深入的说一说声明提升的底层逻辑————预编译。


预编译
预编译发生在函数体内时

当预编译发生在函数体内时简单的就可以概括为以下4点。

  1. 创建函数的AO对象(Action object)//函数的作用域
  2. 找形参和变量声明,将形参和变量声明作为AO的属性名,值赋为undefined。
  3. 将形参和实参统一。
  4. 在函数体内找函数声明,将函数名作为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(){}
    var d=a
    function d(){}
    console.log(d)//123
}
fn(1)

QQ图片20231109233522.png a作为形参,b,d作为变量声明被传入AO作为AO属性名并且被赋值为undefined。


QQ图片20231109235719.png a作为形参先传入的值为1所以a由undefined变为1,接着是a,d都作为函数声明被传入AO,a的值再次变为function a(){},b变为function。
至此,预编译全部完成,接下来就是代码的运行了。

QQ图片20231110000733.png 图中标出了a的值的传递过程,首先我们需要明确知道,代码的运行过程是不会再运行变量和函数的声明,因为这一部分是编译的内容,图中红线部分就是全部运行过程需要处理的代码了。代码由上至下运行,首先输出a的值此时a为function a(){},接着a被赋值为123,再次输出a的值,此时a的值则是123。第5行的函数声明在预编译阶段已经完成所以代码运行阶段不运行。所以第6行的a输出仍然为123。剩余代码不多赘述。

预编译发生在全局时

首先仍然是先介绍预编译发生在全局时的基本规则

  1. 创建一个GO对象(全局作用域)
  2. 找变量声明,将变量名作为GO的属性名,值赋予undefined
  3. 在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体

相较于在函数内的预编译更简单一点。通过以下的代码来理解发生在全局的预编译。

global =100
function fn(){
    console.log(global);//undefined
    global=200;
    console.log(global)//200
    var global=300
}
fn()
var global;

QQ图片20231110003640.png 在全局预编译时,找变量声明只有一个global,但是注意第一行仍然只是一个赋值语句,预编译阶段不执行,是最后一行才是一个变量声明传入GO中并将值赋为undefined,然后在全局找函数声明则是第二行的fn传入GO。此时预编译阶段就结束了。接着是代码的执行阶段,global被赋为100,然后执行fn()函数。此时,又是函数内的预编译了。生成一个AO作用域,fn()函数最后一行声明global传入AO中赋值为undefined。然后预编译阶段就结束直接进入运行阶段。

尾声

对预编译这种JavaScript的底层的原理的学习,能让我们在一个新的维度去审视JavaScript的代码,对于JavaScript有了更深的理解,对于面试时一些绕来绕去的代码题想必能信手拈来,希望能对大家的学习和工作有些帮助。