在深入探讨JavaScript预编译与执行机制之前,我们首先需要明确几个基本概念:声明提升、函数执行上下文、全局执行上下文以及调用栈。这些概念共同构成了JavaScript运行时环境的核心组成部分,对于理解代码的执行流程至关重要。本文将围绕这些核心概念,展开一次深度解析之旅,全面而深入地阐述这一主题。
一、声明提升(Hoisting)
声明提升是JavaScript中一个独特的特性,它涉及到变量声明和函数声明在代码执行前被提前处理的过程。这一特性有时会引发一些令人困惑的行为,尤其是对初学者而言,但深入理解其机制是掌握JavaScript语言的关键之一。
1. 变量声明提升
当JavaScript引擎开始执行一段脚本或函数时,它首先会进行编译阶段,在这个阶段,所有使用var
声明的变量会被提升至当前作用域的顶部。这意味着尽管你在代码中可能将变量声明放在了函数体的下半部分,实际上该变量在整个函数作用域内都是可用的,尽管其初始值为undefined
。
console.log(a); // undefined var a = 5
在上述代码中,尽管变量a
的赋值操作发生在console.log
之后,但由于声明提升,a
在输出时已经被声明了,只是其值为undefined
。
结果与下述代码一致
var a console.log(a); // undefined var a = 5
2. 函数声明提升
与变量声明类似,使用函数声明语法定义的函数也会被提升至所在作用域的顶部。这意味着你可以在函数声明之前调用函数,而不会遇到引用错误。
javascript
foo(); // 输出 "Hello, World!" function foo() { console.log("Hello, World!"); }
二、函数执行上下文与全局执行上下文
在JavaScript中,每当一个函数被调用时,都会创建一个新的执行上下文(Execution Context)。执行上下文负责存储函数执行过程中的变量、函数参数以及作用域链等信息,它是JavaScript执行环境的具体体现。
1. 函数执行上下文(AO)
当一个函数被调用时,会执行以下步骤来创建其执行上下文(通常称为活动对象AO, Activation Object):
- 创建AO:为函数创建一个新的执行上下文对象AO。
- 参数和变量声明:找形参和变量声明,将形参和变量名作为AO的属性,值为underfined初始值设为
undefined
。 - 实参与形参绑定:将实参和形参统一。
- 函数声明:在函数体内找函数声明,将函数名作为AO的属性名,值赋予函数体。
例子
function fn(a){
console.log(a);
//function a(){}
var a = 123 console.log(a);
// a:123 function a(){}
//函数声明 console.log(a);
var b =function (){}
//函数表达式 console.log(b);
//function b(){}
function d(){}
var d=a
// d:123
console.log(d); //123 } fn(1)
执行上下文AO:
代码实际输出结果:
2. 全局执行上下文(GO)
全局执行上下文是在程序启动时创建的第一个执行上下文,它代表了整个JavaScript程序的执行环境。其创建过程包括:
- 创建GO:创建全局执行上下文对象 GO。
- 变量声明:在全局找变量声明,变量名作为GO的属性名,初始值为
undefined
。 - 函数声明:在全局找函数声明,函数名作为GO的属性名,值为函数体。
例子
var global = 100 function fn(){
console.log(global);
}
fn()
执行上下文GO:
代码实际输出结果:
3. 全局执行上下文(GO)和 函数执行上下文(AO)
当程序运行时,先进行全局变量的预编译,在函数调用的前一刻进行函数声明,进入函数预编译。
例子:
javascript
global = 100 function fn(){
console.log(global);
//undefined global = 200
console.log(global); //200 global=300 }
fn();
console.log(global); var global;
全局执行上下文GO和函数执行上下文AO:
代码实际输出结果:
三、调用栈(Call Stack)
调用栈是JavaScript引擎用于追踪函数调用顺序的一种数据结构,它记录了每一次函数调用的状态,包括函数的返回地址和局部变量等信息。每当一个函数被调用时,其执行上下文会被推入调用栈;当函数执行完毕,其上下文则从栈顶弹出,控制权返回到调用它的函数或全局作用域。
调用栈的工作机制确保了函数的执行顺序遵循“先进后出”原则,同时也限制了JavaScript的并发能力,因为任何时刻只能有一个执行上下文处于活动状态,这解释了为什么JavaScript被称为单线程语言。