五分钟带你深入了解JavaScript预编译

109 阅读5分钟

本文的上篇:五分钟带你秒杀JavaScript作用域

  搭配学习效果更佳,欢迎前来学习指正。

关于js声明提升的疑惑

  当你学到js变量提升的时候讲到:

console.log(a)
var a = 1

//在编译时,会这样:
//var a
//console.log(a)
//a=1 
//因此输出的不是报错,而是undefined
  • 变量声明提升到当前作用域顶部,但是赋值操作不会提升

  是的,轻描淡写的一句话,仿佛是吃饭喝水一般理所应当。可是笔者疑惑:变量声明提升具体是怎么进行的?还有没有其他的声明提升?为了解决这个问题,笔者将为大家介绍一下声明提升,并深入讲讲JavaScript预编译。

  声明提升有两种类型:

  1. 变量声明提升,这点我们已经了解过

  2. 函数声明提升

  请看这样一段代码:

var a = 1
function fn() {
  console.log(a);
  var a = 2
  function a() {}
}
fn()

  这段代码的输出结果是function a(){},怎么回事?别着急,这涉及到JavaScript的预编译的知识点,也就是今天我们的话题。

JavaScript预编译

  众所周知,V8引擎工作要经历这样几个步骤:

  1. 分词
  2. 语法分析
  3. 代码生成
  • 代码执行之前,V8引擎会先进行编译,也就是说,预编译发生在代码执行之前

预编译分为两种:函数体内的预编译、全局预编译

函数体内的预编译

  1. V8引擎遇到函数声明时,创建一个执行上下文的对象 AO:{}
  2. 找形参和变量声明,将形参和变量名作为属性名,添加到 AO 中,其值为 undefined
  3. 把形参与实参统一,让实参的值传上去。
  4. 在函数体内找函数声明,将函数名作为AO的属性名,函数体作为属性值,添加到AO中。

AO对象(Activation Object):活动对象。如果你不知道对象是什么,请移步至文末。

  V8引擎与其预编译就像“老板与秘书的关系”。

  V8引擎是执行代码的“老板”、预编译则像提前准备的“秘书”。秘书要提前准备好老板执行业务所需要的各类物品文件,老板则查看准备好的文件夹,按顺序取用里面的文件。

  请看下面一段代码

function fn(a) {
  console.log(a);    
  var a = 123
  console.log(a);    
  function a() {}

  var b = function() {}
  console.log(b);    
  function c() {}
  var c = a
  console.log(c);
}
fn(1)

  一下懵了?别急,我们一步步来。

  1. 看见函数声明,我们想到来创建一个AO对象。

AO{
}

  2. 紧接着,我们找到形参与变量声明,记录下来值为undefined

AO = {
  a: undefined,
  b: undefined,
  c: undefined
}

  3. 别忘了将形参与实参统一,即 a=1

AO = {
  a: undefined 1,      //为方便理解这样写,实际应当是覆盖为1 
  b: undefined,
  c: undefined
}

  4. 找函数声明,函数体覆盖

AO = {
  a: undifined 1 function a() {},  
  b: undefined,
  c: undifined function c() {}  
}

  5. 最后开始执行代码

function fn(a) {
  console.log(a);    //输出function a() {}
  var a = 123
  console.log(a);    //输出123
  function a() {}

  var b = function() {}
  console.log(b);    //输出functionb() {}
  function c() {}
  var c = a
  console.log(c);//123
}
fn(1)

  恭喜你,学会了函数体内的预编译。不仅函数体内有预编译,在全局下也会进行预编译,下面我们来聊聊全局预编译。

全局预编译

  1. 创建一个全局的执行上下文(对象) GO:{}

  2. 找全局变量声明,将全局变量名作为属性名,添加到 GO 中,值为 undefined

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

  纸上得来终觉浅,知道了还不来段代码?请看:

var a 
var b = 2
function a () {
  console.log(a);   
  var c = 3
  var a = b       
  function c() {}
  console.log(c);      
}
a()               
console.log(a);    

  1. 还是来创建一个对象,全局的执行对象GO:{}在页面加载时创建。

GO = {
};

  2.然后找全局变量声明,值为undefined

GO = {
  a: undefined,   
  b: undefined     
};

3. 找全局函数声明

GO = {
  a: undefined function a(){},   
  b: undefined     
};

  4. 执行,要执行a()了。函数调用a(),创建AOAO创建过程还记得吗?一口气来一遍:

// 创建空 AO
    AO = {};

    // 找形参和变量声明
    AO = {
      c: undefined,   // var c
      a: undefined    // var a
    };

    // 找函数声明
    AO = {
      c: undefined function c() {},  
      a: undefined
    };

  5. 执行函数内部代码,再返回全局,输出详情如下   

undifined
3
[Function:a]

GO对象(Global Object):全局对象。

  很好,你现在已经掌握了全局预编译的必要知识。不难发现:全局预编译发生在函数体预编译之前。

  至此,JavaScript的预编译已经讲完了,再看开头那段代码,疑惑已经迎刃而解。

总结

  1. 预编译发生在执行之前,全局预编译发生在函数体预编译之前
  2. 变量提升只提升声明,不提升赋值
  3. 函数声明完整提升,优先级高于变量
  4. AO 管理函数内部,GO管理全局

补充:什么是对象?

  在JavaScript中,对象是一个键值对的集合,可以用来描述一个事务的属性和行为。例如:

var cat = {
  name: "乐乐猫",
  age: 4,
  isFat: true,
  hobbies: ["乐乐", "吃猫粮"],
};
  

  这里我创建了一个对象,cat。在里面我定义了许多属性,有name、age、isFat、hobbies,并为这些属性赋好了值。

  对象的基本操作:增、删、改、查,下面我将为你逐个演示如何操作。

  增加一个属性,这是一只公猫。

cat.gender = "公"

  改变一个属性,它成长了一岁。

cat.age = 5;

  删除一个属性,它减肥了。

delete cat.isFat;        

  现在输出这些属性

console.log(cat)

  得到一只公猫,名为乐乐猫,5岁,喜欢乐乐、吃猫粮。

{ name: '乐乐猫', 
age: 5, 
hobbies: [ '乐乐', '吃猫粮' ], 
gender: '公' }

  附乐乐猫图,希望读者天天都乐乐。如果本文对你有帮助,欢迎点赞、收藏、关注。你的支持是我创作的动力!🌹🌹

lele.png