前言
在许多大厂的面试中,js的预编译经常会被面试官提及,今天,小编就带大家来深入理解一下js预编译的详细过程。
首先咱们来看一个案例:
var global = 100
function fn(){
console.log(global);
}
fn()
在面试时,面试官问你这段代码的执行结果时,你可能会脱口而出“100”,此时,面试官来接着问你原因,问你为什么?你也可能马上答出说在函数内部作用域里找不到global这个变量的值于是去全局作用域里找到一个变量global=100,所以打印输出的值为100.答案当然是对的,但是在面试官眼里可能不太满意,如果这题10分,在面试官眼里可能只会给你六七分。因为你回答的不够深入,没有说到面试官想要的答案。那么,面试官想要的答案是什么呢?今天小编就带大家来深入理解一下js的预编译过程,看完这个,相信你就知道答案了。
一、什么是预编译?
在JavaScript中,预编译是代码执行前进行的一项操作。具体来说,预编译会把变量声明和函数声明提前,并按照一定的规则将这些声明放在创建的对象中。这个过程主要分为全局预编译和局部预编译,全局预编译发生在页面加载完成时,而局部预编译则发生在函数执行的前一刻。 简单来说,就是代码在执行前需要进行编译操作,用于确定代码之间的各种关联。
二、预编译的作用是什么?
预编译阶段主要是完成函数声明和变量声明的提升,但没有初始化行为(即赋值)。需要注意的是,匿名函数不参与预编译。在预编译过程中,JavaScript会在内存中开辟一块空间,用来存放这些变量和函数。预编译的存在有助于JavaScript引擎更有效地执行代码,因为它允许引擎在代码实际执行之前进行一些优化和准备工作。
首先给出我压箱底的预编译过程,句句重点!(敲黑板!)
发生在全局的预编译:
1.创建GO(Global Object)对象
2.找变量声明,将变量名作为GO的属性名,值为undefined
3.在全局找函数声明,将函数名作为go的属性名,值为该函数体
发生在函数体内的预编译:
1.创建一个AO(Action Object)对象
2.找形参和变量声明,将形参和变量名作为AO的属性名,值为undefined
3.形参和实参统一
4.在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体
案例一:
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 c = a
console.log(c)
}
fn(1)
输出结果:
这题只有一个函数体,所以我们主要是对函数体的预编译:
步骤一:创建AO对象
AO{
}
步骤二:找到形参和变量声明后将其作为AO的属性名并赋值为undefined
AO{
a:undefined ->undefined
b:undefined
c:undefined
}
这里给大家解释一下a这个属性,代码从上到下,先是因为fn(a)形参的原因赋值为undefined,后是因为var a声明的原因再次赋值为undefined,两次重复声明,取最新的,最后还是undefined
步骤三:形参与实参值统一
AO{
a:undefined->1
b:undefined
c:undefined
步骤四:在函数体内找函数声明,将函数名作为AO的属性名,值为该函数体
AO{
a: 1 -> function: a
b: undefined
d: undefined ->function: c
}
这里咱们需要注意,像function a(){}这样的才是函数声明,如果有=叫做函数表达式,比如var a = function(){}
好,这里我们的预编译工作已经完成
现在开始执行
AO{
a: function: a 输出 -> 123 输出 输出
b: undefined -> function: b 输出
c: function: c -> 123 输出
}
执行的时候是执行带有=的赋值语句和console.log等执行语句.
案例二:
function test(a, b) {
console.log(a);
c = 0
var c;
a = 3
b = 2
console.log(b);
function b() {}
console.log(b);
}
test(1);
输出结果:
此题相比第一题多了直接赋值语句,像c=0这种。该题还是主要发生在函数体内
步骤一:创建AO对象
AO{
}
步骤二:找形参和变量声明后将其作为属性名并赋值为undefined
AO{
a:undefined
b:undefined
c:undefined
}
步骤三:形参和实参相统一
AO{
a:undefined->1
b:undefined
c:undefined
}
步骤四:找函数声明将函数名作为属性名,值为该函数体
AO{
a:1
b:undefined->function b(){}
c:undefined
}
现在开始执行:
AO{
a:1 输出 ->3
b:function b(){} ->2 输出 输出
c:undefined ->0
}
案例三:
global = 100
function fn(){
console.log(global)
global = 200
console.log(global)
var global = 300
}
fn()
var global
该题既有全局又有函数体,所以咱们先对全局进行分析: 步骤一:创建GO对象
GO{
}
步骤二:找表量声明并赋值undefined
GO{
global:undefined
}
步骤三:在全局找函数声明作为GO的属性名,值为该函数体
GO{
global:undefined
fn: function fn(){}
}
现在开始执行:
GO{
global: undefined -> 100
fn: function: fn
}
执行函数前也需要编译,所以现在咱们对函数体进行预编译 步骤一:创建一个AO对象
AO{
}
步骤二:找形参和变量声明将其作为属性名并赋值为undefined
AO{
global:undefined
}
步骤三:形参和实参相统一
AO{
global:undefined
}
步骤四:在函数体内找函数声明,并赋值为该函数体
AO{
global:undefined
}
这里的步骤三、四都没有变化,因为函数体中没有形参和函数声明,现在开始直接执行函数
GO{
global:undefined-> 100
fn:function fn(){}
}
AO{
global:undefined 输出 ->200 输出 ->300
}
所以这里的输出结果为undefined和200.相信说到这里大家应该会做这类题目啦。 那咱们在回头来看文章开头提到的那道题目,会不会有更深的理解
var global = 100
function fn(){
console.log(global);
}
fn()
首先V8引擎会先创建一个GO对象,然后再全局作用域里找变量声明global,并作为GO的属性名,赋值为undefined,然后在全局作用域里找函数声明fn();将其作为GO的属性名并赋值为该函数体存入GO中,这样GO对象中就有属性global和fn(),接下来在全局执行,将global的值赋予100,接下来进行函数体fn的编译,先创建一个AO对象,然后再函数作用域下找形参和变量声明将形参和变量名作为AO的属性名,值为undefined,接下来形参和实参统一,最后再函数体内找函数声明,将函数名作为AO的属性名,值为该函数体存入AO中,且该AO对象指向刚刚的GO对象,可惜这题里面都没有后面几个步骤,编译完成后就进行执行,输出global的值,会先再当前自己的函数作用域里查找,找不到的话就会往全局作用域里找,如果全局作用域里也没有,则该程序就会报错,最后再全局作用域里找到一个global值为100,于是便打印输出100.如果你是这样回答面试官的,那么恭喜你,这道题面试官就会觉得你彻底理解了。
这里还要给大家介绍一个新的名词——调用栈
JavaScript的调用栈是一种栈结构,用于存储计算机程序执行时其活跃子程序的信息。它遵循后进先出的原则。在JavaScript中,每当一个函数被调用时,引擎会生成一个栈帧,该栈帧保存了函数的执行上下文(包括函数的参数、局部变量以及函数的返回地址等信息),然后将这个栈帧压入调用栈。当函数执行完成后,相应的栈帧会从栈顶弹出,控制权交回给上一个调用的函数。
其实可以理解为被阉割后的数组,它只能先进后出或者后进先出。当一个js文件中有既有全局作用域又有函数作用域的时候V8引擎会先将全局作用域放入栈底,这里我们管它叫全局执行上下文,函数作用域这里叫函数执行上下文。
全局执行上下文有两类,一类是变量环境,专门用于存放像var变量的声明,另一类是词法环境,专门用来存放let和const变量,所以这里就是let和const不会声明提升的底层原因。
全局执行上下文放入栈底后开始执行操作,global = 100和 fn的调用。之后函数执行上下文入栈函数执行上下文也有自己的变量环境,词法环境,存放内容与全局执行上下文一致,调用栈有个指针,指向当前在哪个上下文中执行,它会先在函数执行上下文中找词法环境,然后去到变量环境,找不到的话指针就下移,到全局执行上下文,先找词法环境,后找变量环境。
说到这里相信小伙伴们对js的预编译都有了一个更深入的理解吧。如果觉得我有理解不对的地方欢迎大家批评指正,如果觉得本文对你有帮助的话欢迎大家一键三连!感谢感谢!