js中编译器编译代码的实际过程到底是怎么一回事呢?
它和其他编程语言预编译区别在哪?
为什么当引擎在为函数内部变量赋值时寻找不到变量声明为什么会往外找(来到全局作用域)?
我们可能只是知其然,而面试官希望知其所以然,也就是知道更深层的知识。别急我们现在一步一步来弄清楚js的预编译机制
一、预编译发生在全局的三部曲
- 创建GO对象(Global Object):当编译器开始编译整个js代码时,首先会创建一个执行上下文对象GO对象(Global Object)
- 找变量声明: 将变量声明作为GO对象的一个属性名,其值初始化为undefined
- 在全局找函数声明 : 将函数名作为Go对象的属性名,其值初始化为该函数体 听起来很抽象,我们来看看一段代码的来剖析实际编译运行过程:
global=100
function fn(){
console.log(global)
global=200
console.log(global)
var global=300
}
fn()
var global
我们按照步骤一步一步分析:
- 当编译器拿到这段代码时第一件事就是先创建好一个GO对象
go{
}
- 寻找变量声明:整个代码只有“var global”这个变量声明,编译器记下global,并将其作为GO对象的一个属性并初始化值为undefined
go{
global:undefined
}
3.寻找函数声明:整个代码只有“function fn(){...}”这个函数声明,同样编译器,记下fn,并将其作为GO对象的一个属性但是初始化值为该函数体
go{
global:undefined
fn: fn函数体
}
全局预编译完成之后,引擎便会开始执行代码
但是在执行函数时也存在编译即动态编译,之后引擎才能继续执行代码
二、编译发生在函数执行之前的四部曲
- 创建AO对象(action object):编译器编译一个函数时都会为函数创建一个AO(action object)上下文执行对象
- 找形参和变量声明:将变量声明和形参的作为AO的属性名,值为undefined
- 将形参和和实参的值统一:将传入实参的值赋给形参
- 找函数声明:在函数体内寻找函数声明,将函数名作为AO对象的属性名,值赋予函数体
我们继续分析上面代码的执行过程:
全局预编译完成,引擎开始执行代码:将100赋值给global,执行函数fn,引擎便开始让编译器编译该函数内部代码
global=100
function fn(){
console.log(global)
global=200
console.log(global)
var global=300
}
fn()
var global
一样我们按照步骤来:
- 编译器拿到函数fn的代码先创建AO对象
AO{
}
- 寻找fn中形参和变量声明并将其作为AO对象的一个属性值并将其初始化值为undefined,没有形参,只有一个变量声明“var global ”
AO{
global:undefined
}
- 将传入实参的值赋给形参,没有形参也没有传入的实参
- 寻找内部函数声明没有函数
我们继续补全代码执行过程:
函数内部预编译完成开始执行函数:
- console.log(global)打印undefined;
- global=200 ,global被赋值为200;
- console.log(global) 输出200
- var global=300 global最后被赋值为300
- 函数最终输出结果为undefined,200
执行函数内部代码时,如果引擎需要给变量赋值,便会去找该变量的位置。我们都知道函数内部找不到便会去函数外部找,但是面试官想让你回答的是
其内部的机制是怎么样的呢?于是引出调用栈
三、调用栈
- 执行代码先入栈GO执行对象 ,再入栈AO执行对象
- 调用栈指针 控制了执行顺序,function中找不到变量声明会指向GO对象中再去找。而且会先找词法环境再找词法环境
为什么当引擎在为函数内部变量赋值时寻找不到变量声明为什么会往外找(来到全局作用域)?
编译器完成GO对象的属性创建之后,GO对象入栈,调用栈指针指向GO对象,引擎开始执行GO对象,如果要执行函数,便会在执行函数前生成一个AO对象入栈;
调用栈指针指向AO对象,引擎开始执行AO对象,在对函数内执行赋值语句,引擎便会开始在AO对象的环境内寻找该变量声明,并按照先词法环境后变量环境的顺序寻找如果没有,调用栈指针便会指向GO对象在该对象的环境内寻找。所以说是引擎在为函数内部变量赋值时寻找不到变量声明为会往外找(来到全局作用域)
四、练习
看完知识之后感觉还不错,来做两道面试题练习一下~
请判断以下代码执行结果
1.
function foo(a,b){
console.log(a)//1
c=0//创建全局变量
var c
a=3
b=2
console.log(b)
function b(){}
function d(){}
console.log(b)
}
foo(1)
2.
function fn(a){
console.log(a)
var a=123
console.log(a)
function a(){}
console.log(a)
var b=function(){}
console.log(b)
var d=a
console.log(d)
}
fn(1)
答案:
- 122
步骤 1 2 3 4 执行
AO{
a : u u->1 1->3
b : u u->f f->2
c : u u->0
d:f
}
ps:u=undefined,f为函数体;u->1,值从undefined覆盖为1
function foo(a,b){
console.log(a)//输出1
c=0//创建全局变量
var c
a=3
b=2//被覆盖为2
console.log(b)//输出2
function b(){}
function d(){}
console.log(b)//输出2
}
foo(1)
- [Function: a], 123, 123, [Function: b], 123
步骤 1 2 3 4 执行
AO{
a : u u->1 1->f f->123
b : u u->f
d : u u->123
}
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() b{}
var d=a
console.log(d);//输出123
}
fn(1)
码字不易如果觉得对你有帮助的话,麻烦点个小小的赞和关注,激发下博主的创作动力;关注博主以后更新更多的前端面试知识😊