学会js中的预编译,代码再也恶心不到我了!
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中的一个基本的知识点——预编译。我们前面讲到了程序的执行分为编译和执行,而我们要讲的预编译则是发生在执行之前,又称作 解析或准备阶段。它主要涉及以下几个方面:
-
变量和函数声明的提升(Hoisting) :
- 变量声明:使用
var
、let
、const
声明的变量会被提升至所在作用域的顶部,但需要注意的是,let
和const
声明的变量虽然在逻辑上被提升,但实际上在声明之前访问它们会引发引用错误。 - 函数声明:使用函数声明语法(如
function foo() {}
)定义的函数会整体被提升至作用域顶部,可以在声明之前调用。
- 变量声明:使用
-
创建执行上下文(Execution Context) :
- 在JavaScript中,每当一个函数被调用或者全局代码被执行时,都会创建一个新的执行上下文。
全局的预编译
当一个程序准备执行时,JavaScript引擎(我们俗称V8引擎)会为其创建一个新的执行上下文对象,具体的步骤如下:
- 创建全局执行上下文对象(Global Object, GO): 即
GO:{
}
注意:笔者只不过是在为了让大家能够更直观地感受创建执行上下文对象,实际上在代码中并没有此代码片段
- 找变量声明,变量名作为GO 的属性名, 值为undefined:所有使用
var
声明的全局变量(不包括let
和const
)会被添加到GO作为属性,初始值为undefined
。尽管let
和const
也会在执行上下文中声明,但它们不会被提升到可访问状态。 - 在全局找函数声明,函数名作为GO的属性名,值为函数体:全局范围内使用函数声明方式定义的函数,其名称同样作为GO的属性,值为函数体。这意味着全局函数可以在声明之前被调用。
下面将借一段简单的代码来解释一下全局的预编译过程:
var global = 100
function fn() {
console.log(global);
}
fn();
这是一段在全局作用域的代码,当我们执行前首先进行预编译,先创建一个GO对象,然后再找变量声明,在这里声明了一个global
变量,将其作为Go
的属性,赋值为undefined
。
最后找函数声明,这里声明了一个函数fn,所以将fn作为GO
的属性,值为function fn() {}
GO:{
global:undefined,
fn:function fn() {}
}
到这里全局的预编译步骤就已经做完了,程序开始执行。
有人说:“诶,你的函数还没有编译呢,怎么就开始执行了”。
其实在js中是先编译后执行的,编译往往在执行的前一刻。也就是说,当我们进行完全局的预编译后,就直接开始执行,将global
的值赋值为100,然后再调用函数fn
,当V8引擎发现此时这个函数还没有编译时,于是就开始编译这个函数。
函数的预编译
与全局的预编译相同,在编译函数时,V8引擎会创建一个函数的执行上下文对象,具体步骤如下:
- 创建激活对象(Activation Object, AO) :这是函数特有的执行上下文对象,用于存储函数执行时的局部变量、参数和内部函数。
- 找形参和变量声明,将形参和变量名作为AO的属性名,值为undefined:形参作为AO的属性,其属性名即为形参名。同时,将函数内声明的变量(使用
var
、let
、const
)添加为AO的属性,值初始化为undefined
(let
和const
在实际赋值前不可访问)。 - 将实参和形参统一:根据调用时提供的实参值填充形参变量。
- 在函数体找函数声明,将函数名作为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。
此时运行出来的结果与我们刚刚推出来的结果一毛一样。
学习成果检验
通过刚刚详细的讲解了一下全局的预编译和函数的预编译,相信大家对预编译这一知识点有了一定的掌握。为了看看大家的掌握程度,下面我们来看一下这段综合的代码,大家可以自己试着推一下结果是多少!!
global = 100
function fn() {
console.log(global);
global = 200
console.log(global);
var global = 300
}
fn()
console.log(global) //;
var global;
先不要往下看了,做完了再往下看
正确结果是
你们做对没嘞!大佬可以留言秀秀自己的答案哦。
结束语
讲到这里其实已经结束了,预编译是js中的一个基础点,同时也是一个重点,可是会有很多的面试官出一些恶心的代码来考你的哟!!但是,当我们把预编译这个知识点掌握透了,我们就再也不怕面试官出这些恶心的代码了!
加油,兄弟们!大厂在向我们招手!!!
转载自:https://juejin.cn/post/7373302995461013539