JavaScript在代码执行前,会对代码进行一系列的预编译操作,其中包括声明提升和创建执行上下文。这些机制在编写和调试代码时非常重要。本文将深入探讨声明提升、函数和全局的预编译过程,以及调用栈的作用。
预编译
声明提升
我们先来看一段代码
console.log(a);
var a = 1;
很多人应该会认为编译器会报错吧,但实际情况是不仅没有报错,还输出了值——undefined
这是因为编译器在执行代码前,会先进行预编译,产生声明提升,所以代码在编译器眼里其实是下面这样的
var a; //变量声明提升
console.log(a);
a=1;
函数同理
foo() //function foo(){
function foo(){ // var a = 1
var a = 1 // console.log(a);
console.log(a); //}
} //foo() 函数声明,整体提升
- 声明提升
- 变量声明,声明提升
- 函数声明,整体提升
函数中预编译的过程
理解了声明提升后,我们再来思考一段代码
var a = 1;
function foo(a){
var a = 1
function a(){}
console.log(a);
}
foo(3)
要分析上面的代码,只知道声明提升是不够的,因为声明提升只是预编译产生的一种现象,所以你还需要知道预编译真正的规则,接下来我们就来讲一讲函数中预编译到底发生了什么。
- 函数中的预编译
- 创建函数的执行上下文对象 AO(Activation Object)
- 找形参和变量声明,将形参和变量名作为AO的属性,值为undefined
- 将实参和形参统一
- 在函数体内找函数声明,将函数名作为AO属性名,值赋予函数体
我们按上面的步骤逐一分析刚刚的代码:
- 首先创建函数的执行上下文对象AO,然后找到形参
a和变量声明var a,值为undefined - 将实参和形参统一,即
a = 3 - 最后找函数声明,
a = function a(){},到这里预编译就完成了。 - 接下来就是执行了,要执行的只有
var a = 1,所以现在a的值就为1,最后执行输出a的值,也就是输出1了
注意这里的var a = 1说的是函数体内的,全局的var a = 1已经在函数的预编译前处理完了,具体可以看下面全局预编译的过程。
总体就是下面这样
AO:{
a:undefined -> 3 -> function a(){} -> 1
}
如果将输出语句放到函数体的最前面,最终结果是输出[Function: a]
var a = 1;
function foo(a){
console.log(a);
var a = 1
function a(){}
}
foo(3)
因为输出的在执行var a = 1前,而此时经过预编译a的值为function a(){}
全局中预编译的过程
理解了函数的预编译,其实全局的预编译也就没问题了,全局预编译只是比函数少了个将实参和形参统一。
全局的预编译
- 创建全局执行上下文对象 GO(Global Object)
- 找变量声明,变量名作为GO的属性名,值为undefined
- 在全局找函数声明,函数名作为GO的属性名,值为函数体
- 要特别注意的是,预编译是发生在执行前一刻的,所以函数声明时并不会进行预编译。
现在让我们来练习一道题
global = 100
function fn(){
console.log(global);
global = 200
console.log(global);
var global = 300
}
fn()
最后输出的值是什么呢?
解毒:
- 首先全局进行预编译,没有任何变量声明,继续找函数声明,找到
fn,值为functio(){} - 预编译完成后,执行函数
fn() - 函数执行前先要进行预编译,函数的预编译中找到
global,值为undefined,预编译结束。 - 执行函数,此时
global值为undefined,所以输出undefined,然后执行global=200,再输出200,最后执行global=300.
所以输出的值为200,而global最后的值为300.
调用栈
最后我们还需要知道的是,全局和函数创建的执行上下文对象是存放在一个调用栈中的,可以简单理解为是用来管理函数调用关系的一种数据结构。而浏览器的调用栈往往是非常小(为了浏览器的运行速度),那如果经常调用函数的话不是会很容易爆栈吗,所以当一个函数执行完后,他的执行上下文是会被销毁的,全局也一样。
总结
理解预编译过程对于写出健壮的JavaScript代码至关重要。这些概念解释了为何某些变量和函数在未明确声明之前就可以使用。此外,调用栈的概念有助于理解函数调用的顺序和如何调试代码。通过深入理解这些机制,开发者可以更好地掌握JavaScript的工作原理,避免常见的错误和误区。