引言
前面我们掌握了一部分JavaScript的基础用法,但是对于要冲击大厂的各位来说,掌握一门语言的使用还不够,还应当理解一些底层的运行机制。JavaScript作为一门广泛应用于前端开发的脚本语言,其执行过程分为两个阶段:预编译阶段和执行阶段。理解JavaScript的预编译机制和执行上下文是深入掌握这门语言的关键。本文将深入探讨JavaScript中的预编译过程,解析变量和函数的声明提升,并结合实际代码示例详细讲解全局和函数级上下文的创建与管理,以及调用栈的运行机制。
目录
-
预编译与声明提升
-
函数中的预编译
-
全局预编译
-
调用栈与执行上下文
-
总结
预编译与声明提升
在JavaScript中,变量和函数的声明会在代码执行之前被提升(Hoisting)到其作用域的顶部。这意味着变量和函数可以在它们定义之前被引用。
变量声明提升
console.log(a); // 输出: undefined
var a = 1;
在预编译阶段,JavaScript引擎将变量声明var a提升到当前作用域的顶部,但不会提升其赋值部分。因此,在执行console.log(a)时,a的值为undefined。
函数声明提升
foo(); // 输出: "Hello, world!"
function foo() {
console.log("Hello, world!");
}
函数声明会整体提升到作用域顶部,因此可以在函数声明之前调用它们。
函数中的预编译
函数预编译过程涉及创建函数执行上下文对象(Activation Object, AO),并处理形参、变量和函数声明。
示例代码
function fn(a) {
console.log(a); // 输出: function a() {}
var a = 123;
console.log(a); // 输出: 123
function a() {}
console.log(a); // 输出: 123
var b = function() {};
console.log(b); // 输出: function() {}
function d() {}
var d = a;
console.log(d); // 输出: 123
}
fn(1);
预编译阶段 AO 对象的变化
-
创建AO对象:
AO = {} -
处理形参和变量声明:
- 形参
a:AO = { a: undefined } - 变量
a:AO = { a: undefined } - 变量
b:AO = { a: undefined, b: undefined } - 变量
d:AO = { a: undefined, b: undefined, d: undefined }
- 形参
-
将形参和实参统一:
AO = { a: 1, b: undefined, d: undefined } -
处理函数声明:
- 函数
a:AO = { a: function a() {}, b: undefined, d: undefined } - 函数
d:AO = { a: function a() {}, b: undefined, d: function d() {} }
- 函数
执行阶段
- 执行
console.log(a):输出function a() {} - 赋值
a = 123:AO = { a: 123, b: undefined, d: function d() {} } - 执行
console.log(a):输出123 - 执行
console.log(a):输出123 - 赋值
b = function() {}:AO = { a: 123, b: function() {}, d: function d() {} } - 执行
console.log(b):输出function() {} - 赋值
d = a:AO = { a: 123, b: function() {}, d: 123 } - 执行
console.log(d):输出123
全局预编译
全局预编译类似于函数预编译,但作用于全局作用域,创建全局执行上下文对象(Global Object, GO)。
示例代码
var global = 100;
function fn() {
console.log(global); // 输出: undefined
}
fn();
预编译阶段 GO 对象的变化
-
创建GO对象:
GO = {} -
处理变量声明:
- 变量
global:GO = { global: undefined }
- 变量
-
处理函数声明:
- 函数
fn:GO = { global: undefined, fn: function fn() {} }
- 函数
-
赋值
global = 100:GO = { global: 100, fn: function fn() {} }
调用栈
什么是栈?
栈(Stack)是一种数据结构,它遵循“后进先出”(LIFO, Last In First Out)的原则。栈的操作主要包括:
- 压栈(unshift) :将一个元素压入栈顶。
- 弹栈(shift) :将栈顶的元素弹出。
调用栈
调用栈(Call Stack)是管理函数调用关系的一种数据结构。每当一个函数被调用时,会创建一个新的执行上下文并将其推入调用栈;函数执行完毕后,该上下文会从栈中弹出。
示例代码
function first() {
second();
console.log("First");
}
function second() {
console.log("Second");
}
first();
调用过程
- 调用
first,创建first的执行上下文并推入调用栈 - 在
first内部调用second,创建second的执行上下文并推入调用栈 second执行完毕,弹出second的执行上下文first继续执行,直至完毕,弹出first的执行上下文
调用栈的演示代码
我们通过上面的GO预编译和AO预编译的代码来演示调用栈的运行机制。
示例代码
var global = 100;
function fn() {
console.log(global); // 输出: undefined
global = 200;
console.log(global); // 输出: 200
var global = 300;
}
fn();
调用栈分析
1.全局上下文进入栈中
- 创建全局执行上下文(GO)。
- 处理全局变量和函数声明:
GO = {
global: undefined,
fn: function fn() {}
}
- 赋值全局变量:
GO = {
global: 100,
fn: function fn() {}
}
- 调用fn函数,创建函数执行上下文并推入调用栈
-
在fn函数执行前,创建fn的执行上下文(AO)。
-
处理fn函数中的变量声明:
AO = {
global: undefined
}
- fn函数执行阶段
-
执行
console.log(global): -
查找AO,
global为undefined。 -
赋值
global = 200:
AO = {
global: 200
}
-
执行
console.log(global): -
查找
AO,global为200。 -
赋值
global = 300:
AO = {
global: 300
}
- fn函数执行完毕,函数上下文从调用栈弹出
- 全局上下文从栈中弹出
总结
理解JavaScript的预编译机制和调用栈的工作原理是掌握这门语言的关键。通过创建和管理执行上下文对象(GO和AO),JavaScript引擎能够在代码执行之前准备好所需的上下文信息。调用栈则确保函数调用顺序和上下文管理,使得代码能够按预期执行。希望本文能再深化你对JS这门语言的理解和认知,并在编写和调试JavaScript代码时有所助益。望助你在编程之路越走越远!