JavaScript预编译
写在前面
这篇文章将为大家详细讲解有关JavaScript中预编译指的是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
什么是预编译
在JavaScript中,预编译是指在代码执行之前,JavaScript引擎会将变量和函数的声明提升到当前作用域的顶部。这意味着你可以在声明之前使用变量和函数,而不会出现引用错误。预编译分为全局预编译和局部预编译,全局预编译发生在页面加载完成时执行,而局部预编译发生在函数执行的前一刻。
用例说明
首先我们来看个小知识:
console.log(a)
var a = 123
这行代码会打印undefined,而不是报错,这说明程序没有崩溃,只是找不到值而已,这就是var导致的变量声明提升。就可以理解成下面这段代码:
var a
console.log(a)
a = 123
讲完了变量,我们再聊聊函数:
function foo(){
var a=123
console.log(a)
}
foo()
这里我们定义了一个函数foo,上面的代码是在函数体后面调用函数,下面的代码是在函数体后调用,二者都能实现打印,这就是所谓的函数声明,整体提升。
foo()
function foo(){
var a=123
console.log(a)
}
可是当遇到复杂一点的代码,这点知识就不太够了。
var a = 1
function foo(a){
var a=2;
function a(){}
var b = a;
console.log(a);
}
foo(3)
第一次看到这个题,脑子都是嗡嗡的,我们先把题目放在一边。你只要记住以下规律,就在也不怕这类题目了。
局部预编译发生在函数执行之前(四部曲)
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 d =a ;
console.log(d);
}
fn(1);
首先创建AO对象,找形参和变量声明并赋值为undefined:
AO:{
a: undefined,
a: undefined,//覆盖前面的a
b: undefined,
d: undefined,
}
然后将形参和实参的值统一:
AO:{
a: undefined => 1, //a被赋值为实参1
b: undefined,
d: undefined,
}
最后在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体:
AO:{
a: undefined => 1 => function(){}, //函数体内有a的函数声明,a被赋为函数体
b: undefined,
d: undefined => function(){}, //函数体内有d的函数声明,d被赋为函数体
}
至此,函数预编译结束,下面开始执行这段代码:fn(1)开始执行,首先console.log(a),根据上面预编译结果可知,第一个打印为function a(){},a又被赋值为123,第二个打印为123,继续往下看,函数声明function a(){}不执行,第三个打印还是123,var b = function(){}为函数表达式,与函数声明不同,b被赋值为function b(){},所以第四个打印function b(){},d被赋值为a即123,所以第五个打印123。
AO:{
a: undefined => 1 => function a(){} => 123,
b: undefined => function b(){},
d: undefined => function d(){} => 123,
}
看下运行结果,没问题。
至此,我们已经了解了函数预编译的全过程,那么全局预编译是什么样的呢?
预编译发生在全局(三部曲)
1.创建GO对象 (Global Object)
2.找变量声明,将变量声明作为GO的属性名,值为undefined
3.在全局函数找函数声明,将函数名作为GO对象的属性名,值赋予函数体
请看题:
var global = 100;
function fn(){
console.log(global);
}
fn()
首先创建GO对象,找变量声明并赋值为undefined:
GO:{
global: undefined,
}
然后在全局函数找函数声明,将函数名作为属性名,值赋予函数体:
GO:{
global: undefined,
fn: function fn(){}
}
至此,全局预编译已完成,下面开始执行:
global被赋值成100,
GO:{
global: undefined => 100,
fn: function fn(){}
}
函数声明不执行,然后调用fn(),局部预编译发生在函数执行前一刻,开始编译fn(),接着就是进行局部预编译四部曲,创建AO对象,找形参和变量声明,我们发现AO为空。
AO:{
}
那么编译完毕,我们开始执行fn(),打印global,发现在自己的函数作用域的执行上下文中没有global对象,于是去到外层作用域的执行上下文中,最终找到global值为100,最后打印。
那么有同学会问,为什么先找自己的,再找外面的?下面我画图带你理解一下:
当编译器开始编译这份代码的时候,会维护出来一个调用栈(如果有同学对栈有疑惑,我将单独出一期文章来讲解),首先全局执行上下文入栈,进入栈底,全局执行上下文会存放变量环境和词法环境,变量环境存放var声明的变量,词法环境存放let,const声明的变量。
用上一题的例子来看,变量环境应该存放global=undefined,fn=function(),全局执行上下文编译完后开始执行,将global赋值为100。
然后开始调用fn(),于是fn执行上下文进栈,环境为空。
调用栈的指针指向fn执行上下文,先在词法环境中找,找不到global,再进到变量环境中找,还是找不到,于是指针下移指向全局执行上下文,重复上述操作,先在词法环境中找,任然没有,再进到全局执行上下文的变量环境中,最终找到global的值为100。
注:图中的globel为global,小编拼写错误。
结语
希望本文所介绍的预编译概念能够为你在JavaScript编程旅程中带来启示。当你深入了解作用域、变量对象以及函数提升时,你将更加自信地面对JavaScript代码,避免陷入难以调试的错误中。
相关参考
我的Gitee:CodeSpace (gitee.com)
技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!