JavaScript引擎在执行代码时执行代码时会进行三个步骤:
1、语法分析(通篇扫描全局是否有语法错误)
2、预编译
3、解释执行(在预编译之后一行一行执行代码)
下面就是今天的重头戏:
预编译
预编译就是在JavaScript代码解释执行之前,变量和函数的所有声明都会首先被处理。
听上去有点复杂,举个栗子:
console.log( a ); //打印出undefined;
var a = 2;
再比如:
a();//执行函数打印出2
function a(){
conlose.log(“2”);
}
上一个代码片段所表现出来的某种非自上而下的行为特点,就是预编译的结果。
以下来详细的讲述我们的的主角:预编译。
预编译四部曲(函数的预编译):
预编译在函数执行前一刻发生,共4部
1、创建AO对象
2、找形参和变量声明,将变量和形参名作为AO属性名,值为默认值undefind,放在作用域逻辑的最上方。
3、将实参值和形参统一
4、在作用域里面找函数声明,放在作用域逻辑的最上方。
预编译第一步创建的AO对象就是执行期上下文对象,这个对象仅供JavaScript引擎存取,外界无法存取。AO对象在函数执行前一刻创建,并在函数执行结束销毁(闭包除外)。
然后JavaScript引擎就会按照预编译的后3部把变量和函数的声明提升到作用域逻辑的最上方。(这就是通常人们所说的声明提升)。
全局的预编译:
全局的预编译和函数的预编译基本相同:
1、创建GO对象
2、找变量声明,把变量声明作为GO的属性名,值为默认值undefind,放在全局逻辑的最上方。
3、在作用域里面找函数声明,放在全局逻辑的最上方。
全局预编译的会创建GO对象(global object),这个对象会在脚本刚开始运行时创建,直到脚本运行结束或者窗口关闭时销毁(伴随脚本一生)。
注意
1、变量声明提升时,变量的赋值语句不会提升,只有当代码执行到赋值语句时,变量才会赋值。
比如:
console.log(a);//打印出undefined
var a = 3;
console.log(a);//打印出3
第一次打印,因为变量声明提升,但还没有执行赋值语句,所以会打印出undefined。
第二次打印,因为已经执行过赋值语句,所以会打印出3。
2、函数只有声明才会提升,函数表达式不会提升,因此:
a();// 报错:Uncaught TypeError: a is not a function
var a = function(){
alert(213);
}
以上代码会报出Uncaught TypeError: a is not a function,原因就是因为a只有声明提升,函数表达式不会提升,所以a的值是undefined而不是一个函数。
3、预编译时,变量的声明提升在函数的声明提升之前,所以在函数名和变量名相同的情况下,后来的函数的提升会覆盖前面的变量声明提升。
比如:
console.log(a); //打印出函数体
var a = 3;
function a(){
}
console.log(a);//打印出3
因为预编译时函数a把变量a覆盖了,所以第一次打印出函数体a。
之后执行了赋值语句,所以a的值又被赋值为3,所以第二次打印出3。