likes
comments
collection
share

学会js中的预编译,代码再也恶心不到我了!

作者站长头像
站长
· 阅读数 21
var a =1;
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);
}
console.log(a);
console.log(d);
fn(1);

在日常的做题中大家有没有碰到上面类似的代码?

这类代码真的是恶心到我了,你说复杂咩它就反复那几个打印语句,你说他简单呗,又搞不清楚要打印的到底是哪个值。

其实这牵扯到了js中的一个基本的知识点——预编译。我们前面讲到了程序的执行分为编译和执行,而我们要讲的预编译则是发生在执行之前,又称作 解析或准备阶段。它主要涉及以下几个方面:

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

    • 变量声明:使用varletconst声明的变量会被提升至所在作用域的顶部,但需要注意的是,letconst声明的变量虽然在逻辑上被提升,但实际上在声明之前访问它们会引发引用错误。
    • 函数声明:使用函数声明语法(如function foo() {})定义的函数会整体被提升至作用域顶部,可以在声明之前调用。
  2. 创建执行上下文(Execution Context)

    • 在JavaScript中,每当一个函数被调用或者全局代码被执行时,都会创建一个新的执行上下文。

全局的预编译

当一个程序准备执行时,JavaScript引擎(我们俗称V8引擎)会为其创建一个新的执行上下文对象,具体的步骤如下:

  1. 创建全局执行上下文对象(Global Object, GO): 即
GO:{

}

注意:笔者只不过是在为了让大家能够更直观地感受创建执行上下文对象,实际上在代码中并没有此代码片段

  1. 找变量声明,变量名作为GO 的属性名, 值为undefined:所有使用var声明的全局变量(不包括letconst)会被添加到GO作为属性,初始值为undefined。尽管letconst也会在执行上下文中声明,但它们不会被提升到可访问状态。
  2. 在全局找函数声明,函数名作为GO的属性名,值为函数体:全局范围内使用函数声明方式定义的函数,其名称同样作为GO的属性,值为函数体。这意味着全局函数可以在声明之前被调用。

下面将借一段简单的代码来解释一下全局的预编译过程:

var global = 100
function fn() {
    console.log(global);
}
fn();

这是一段在全局作用域的代码,当我们执行前首先进行预编译,先创建一个GO对象,然后再找变量声明,在这里声明了一个global变量,将其作为Go的属性,赋值为undefined。 最后找函数声明,这里声明了一个函数fn,所以将fn作为GO的属性,值为function fn() {}

GO:{
    globalundefined,
    fn:function fn() {}
}

到这里全局的预编译步骤就已经做完了,程序开始执行。

有人说:“诶,你的函数还没有编译呢,怎么就开始执行了”。

其实在js中是先编译后执行的,编译往往在执行的前一刻。也就是说,当我们进行完全局的预编译后,就直接开始执行,将global的值赋值为100,然后再调用函数fn,当V8引擎发现此时这个函数还没有编译时,于是就开始编译这个函数。

函数的预编译

与全局的预编译相同,在编译函数时,V8引擎会创建一个函数的执行上下文对象,具体步骤如下:

  1. 创建激活对象(Activation Object, AO) :这是函数特有的执行上下文对象,用于存储函数执行时的局部变量、参数和内部函数。
  2. 找形参和变量声明,将形参和变量名作为AO的属性名,值为undefined:形参作为AO的属性,其属性名即为形参名。同时,将函数内声明的变量(使用varletconst)添加为AO的属性,值初始化为undefinedletconst在实际赋值前不可访问)。
  3. 将实参和形参统一:根据调用时提供的实参值填充形参变量。
  4. 在函数体找函数声明,将函数名作为AO的属性名,值赋予函数体:在函数体中找到的所有函数声明,其名称作为AO的属性,属性值为对应的函数体。这一步确保函数可以在声明之前被正确引用。

上面那段代码中的函数的预编译太简单了,就仅仅创建了一个AO对象,并没有进行其他的操作。下面我将有一段稍微复杂的代码来为大家展示预编译的效果:

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

首先我们先创建一个AO对象,然后将实参与形参统一,即a变为1,最后找函数声明,将b的值修改为function b() {}

AO:{
    a:undefined  //实参与形参统一,值变为1
    b:undefined  //函数声明,值变为function b() {}
    c:undefined
}

最后开始执行代码,先打印a,此时a值为1,然后将c赋值为0,a赋值为3,b赋值为2,打印b,此时b值为2,再次打印b,值仍为2。

学会js中的预编译,代码再也恶心不到我了!

此时运行出来的结果与我们刚刚推出来的结果一毛一样。

学习成果检验

通过刚刚详细的讲解了一下全局的预编译和函数的预编译,相信大家对预编译这一知识点有了一定的掌握。为了看看大家的掌握程度,下面我们来看一下这段综合的代码,大家可以自己试着推一下结果是多少!!

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

先不要往下看了,做完了再往下看

学会js中的预编译,代码再也恶心不到我了!

正确结果是

学会js中的预编译,代码再也恶心不到我了!

你们做对没嘞!大佬可以留言秀秀自己的答案哦。

结束语

讲到这里其实已经结束了,预编译是js中的一个基础点,同时也是一个重点,可是会有很多的面试官出一些恶心的代码来考你的哟!!但是,当我们把预编译这个知识点掌握透了,我们就再也不怕面试官出这些恶心的代码了!

加油,兄弟们!大厂在向我们招手!!!

学会js中的预编译,代码再也恶心不到我了!

转载自:https://juejin.cn/post/7373302995461013539
评论
请登录