常见的编译语言,如Java,编译步骤分为:词法分析–>语法分析–>语义检查–>代码优化和字节码生成
而对于解释型语言,如JavaScript,通过词法分析–>语法分析–>语法树,就可以开始解释执行了。
js的具体执行过程为:词法分析【将字符流转换为记号流】、语法分析【分析为AST语法树】、预编译、解释执行。
1、词法分析:将字符流转换为记号流
2、语法分析:在函数执行前一刻,将程序大致粗略的扫描一遍,检查是否存在语法错误,然后生成对应的语法树,如:括号是否正确对应等。
3、预编译:当JavaScript引擎解析脚本时,他会在预编译阶段对所有声明的变量和函数进行处理,并且是先预声明变量,再预定义函数!
4、解释执行:在执行过程中,JavaScript引擎是严格按照作用域(scope)机制来执行的,并且JavaScript的变量和函数作用域是在定义是决定的,而不是执行时决定的。JavaScript中的变量作用域在函数体内有效,无块作用域;
function test(){
for(var i = 0;i<3;i++){
执行语句
}
console.log(i);
//i仍然有值,且为3
;但这个在java语言中,则无效
}
JavaScript引擎通过作用域链scope chain把多个嵌套的作用域串联在一起,并借助这个链条帮助JavaScript解释器检索变量的值。这个作用域链相当于一个索引库,并通过编号来存储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象global object为止,如果没有找到值,则传递一个特殊的undefined 值。
scopeTest('global');
function scopeTest(scope){
console.log(scope);
var scope = "local";
function scope(){
}
console.log(scope);
}
//打印结果为:function scope(){}
//
local
可能大多数小伙伴都觉得是global local ,其实不然,这个是预编译导致的情况,那现在我们来说说预编译。
预编译前奏:
1、imply global 暗示全局变量;即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有。
eg:a = 123;
eg:var a = b = 123; //b未经声明就赋值
2、一切声明的全局变量,全是window的属性。
eg:var a = 123; ----->window.a = 123
预编译:
- 创建AO对象 Activation Object 【我们所说的作用域,也称为执行器上下文】
- 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
- 将实参值和形参统一
- 在函数体里面找到函数声明,值赋予函数体
通过预编译的这4个步骤,我们便可以来理解上面那个例题:
预编译发生在函数执行的前一刻,首先按步骤来,第一步,创建AO对象,AO{ };
第二步,按第2条来,形参名为scope,且函数中的变量声明中也有一个scope,虽然两者同名,但是在AO对象中只会存储一个scope,得出 AO{ scope:undefined} ;
第三步,按第3条来,将实参值和形参统一,也就是,将实参中传递过来的值赋值给形参中用到的变量,得出 AO{scope:“global”} ;
第四步,找函数体中的函数声明,值赋予函数体 ,我们很容易看到,函数scopeTest中,包含了一个函数scope的声明且函数名scope与AO对象的scope同名,而AO对象对待同名也只能存储一个,接着按照第4条,将函数体赋给AO对象中的scope,得出 AO{scope:function scope(){ }} ;
预编译结束 ,然后一步一步执行代码。第一行调用scopeTest(‘global’)函数,最开始输出scope的值,电脑会从AO对象里面拿东西,里面scope存储的值为function scope(){}, 然后输出函数。接着继续执行代码,scope重新赋值,scope= “local”; 然后 function scope(){}之前已经在预编译中获取了,当前可不必理会,最后再次需要输出scope中的值,现在AO对象里面为:AO{scope:“local"},所以输出local 。所以最后的结果为:function scope(){} local
通过上面这道题,我们差不多可以理解预编译的过程了,那再完成下面一道题,来巩固一下。
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(){ }
}
fn(1);
//输出结果为:
//
function a(){}
//
123
//
123
//
function(){}
预编译发生在函数执行的前一刻,我们再次按上面的步骤来,第一步,创建AO对象,AO{ };
第二步,按第2条来,形参名为a,且函数中的变量声明中也有一个a,虽然两者同名,但是在AO对象中只会存储一个a,接着还有一个变量声明b,得出 AO{ a:undefined,b:undefined} ;
第三步,按第3条来,将实参值和形参统一,也就是,将实参中传递过来的值赋值给形参中用到的变量,得出 AO{a:1,b:undefined} ;
第四步,找函数体中的函数声明,值赋予函数体 ,我们很容易看到,函数fn中,只包含了函数a声明与函数d声明两个,【注意:这里的函数b不是函数声明式定义,而是表达式定义】接着按照第4条,将函数体赋给AO对象中的对应的属性,得出 AO{a:function(){},b:undefined,d:function(){}} ;
预编译结束 ,然后一步一步执行代码。调用fu(1)函数,最开始输出a的值,电脑会从AO对象里面拿东西,里面a存储的值为function a(){}, 然后输出a,也就是function a(){ }。接着继续执行代码,到var a = 123; 因为预编译时,这里的a已经被声明了,所以只会执行后面的赋值,a重新赋值,a = 123 然后输出a的值;然后到 function a(){ },因为预编译已经经过了声明阶段,现可忽略该行,然后输出a的值,123;接着到 var b = function(){ },同上面的a类似,在预编译时,b已经被声明了,所以只执行后面的函数,现在AO对象里面为:AO{a:123,b:function(){},d:function d(){}},然后输出b,为function (){} 。所以最后得出上面显示的结果。
从上面这个我们便可以理解预编译的过程,在函数内部执行出现的局部作用域会产生AO对象,而在外部的全局作用域会产生GO对象,两者覆盖的范围不一样,全局的GO对象的预编译跟AO对象的步骤、原理差不多,甚至更容易理解。接着举个例子来理解一下:
function test(){
console.log(b);
if(a){
var b = 100;
}
c = 234;
console.log(c)
}
var a;
test();
a = 10;
console.log(c)
首先也是预编译,但现在是先建立GO对象【全局对象,window 对象】,GO{ };然后变量提升,函数提升,GO对象为 GO{a:undefiend,test: function(){}} ;接着一步一步执行代码,声明test函数与声明变量a已经在执行代码前的预编译建立GO对象后被执行过了,现在可忽略,到调用test()函数这行代码,建立局部作用域AO对象,然后按照之前的四个步骤,可以得出AO对象为{b:undefined},然后执行test函数中的代码,首先先输出b的值,undefined;然后进行if判断,局部作用域AO中没有a,所以沿着上面查找,找到GO对象中,在GO对象中有a,且值为undefined,所以if判断结果为假,不执行if中的语句;接着c=234,沿着AO对象 查找,没找到c变量,然后接着往上到GO对象 ,【如果变量未经声明就赋值,此变量就为全局对象所有】,所以将c变量加到 GO对象中,并同时赋值为234,所以现在GO对象为 GO{a:undefiend, test:function(){ }, c : 234 },然后输出c的值,输出的是GO对象中的c的值为234。test函数执行结束,然后接着执行下一句,a = 10,此时的a也是全局对象GO的a,所以GO对象又重新变为 GO{a:10, test:function(){ }, c : 234 },最后一条语句,输出c的值,值为 234,所以最后的输出结果为:undefined 234 234
以上文字均为自己在学习时的总结 ,如有错误,或表述不对的地方,请多包涵,ps:可以在评论区交流哟!
最后
以上就是饱满裙子最近收集整理的关于js中的预编译的全部内容,更多相关js中内容请搜索靠谱客的其他文章。
发表评论 取消回复