上一篇我们描述了js代码运行时底层的大致轮廓,现在我们开始一步一步拨开谜团,继续探索javascript的本质。 这篇我想介绍一下执行上下文和调用栈。
执行上下文
执行上下文是 JavaScript 中的一种抽象概念,它是代码在运行时的环境。每当 JavaScript 代码运行时,都会创建一个执行上下文,用于存储变量、函数、this
的值以及代码执行时的其他信息。
理解执行上下文对掌握作用域、this
绑定、闭包等核心知识至关重要。
执行上下文的类型
JavaScript 中有三种主要的执行上下文:
-
全局执行上下文
- 当 JavaScript 程序开始运行时,默认会创建一个全局执行上下文。
- 它会在整个程序的生命周期中存在,除非程序被终止。
- 特点:
- 全局变量和函数会挂载到
window
(浏览器)或global
(Node.js)对象上。 - 在浏览器中,全局执行上下文的
this
指向window
。
- 全局变量和函数会挂载到
-
函数执行上下文
- 每当调用一个函数时,都会为该函数创建一个单独的执行上下文。
- 每个函数执行上下文都有独立的作用域和变量环境。
- 特点:
- 函数执行上下文会在函数执行时被创建,并在函数执行完毕后被销毁(除非形成闭包)。
- 函数的
this
值根据调用方式动态决定。
-
Eval 执行上下文
- 每当
eval()
函数运行时,会创建一个单独的执行上下文。 - 一般不推荐使用
eval()
,因为它可能导致安全性问题和性能问题。
- 每当
变量对象
变量对象是执行上下文中的核心部分,用于存储变量和函数的声明。变量对象的处理分两个阶段:
-
创建阶段:
- 函数的参数被初始化为变量对象中的属性。
var
声明的变量被初始化为undefined
。- 函数声明被提升(Hoisting),并赋值为函数体。
-
执行阶段:
- 在代码执行过程中,变量被赋予实际的值。
示例:执行上下文和作用域链
var x = 10;
function outer() {
var y = 20;
function inner() {
var z = 30;
console.log(x + y + z);
}
inner();
}
outer();
//最终输出60
执行过程:
-
全局上下文:
- 变量
x = 10
。 - 函数
outer
被声明。
- 变量
-
outer
执行上下文:- 创建变量
y = 20
。 - 函数
inner
被声明。
- 创建变量
-
inner
执行上下文:- 创建变量
z = 30
。 - 执行
console.log(x + y + z)
,访问作用域链中的x
和y
。
- 创建变量
调用栈
调用栈(Call Stack)是一个用于追踪函数调用的栈数据结构,它在程序执行时维护着函数调用的顺序和状态。在JavaScript中,调用栈用于管理函数的执行上下文,确保程序按照正确的顺序执行,并在函数执行完毕后返回到上一个调用的上下文。
调用栈的工作原理
-
栈的结构:
- 调用栈是后进先出(LIFO,Last In First Out)结构。当一个函数被调用时,它的执行上下文被推入栈顶;当函数执行完成时,它的执行上下文被弹出栈。
-
全局上下文入栈
- 当 JavaScript 引擎开始执行时,会首先创建全局执行上下文,并将其推入执行上下文栈。
-
函数上下文入栈
- 每当调用一个函数时,都会为该函数创建一个新的执行上下文,并将其推入栈顶。
-
函数上下文出栈
- 当函数执行完毕,函数上下文会从栈顶弹出,控制权返回到下一个执行上下文。
-
返回和弹出:
- 当函数执行完成,返回值会被传递到调用它的上下文,同时该函数的执行上下文会从调用栈中弹出,控制权返回到栈顶的执行上下文。
小结
为了让js代码能够有序且高效地执行(设计了这么一套流程),代码在执行前会创建好执行上下文(运行环境),在执行上下文中声明的变量和函数等信息会被收集进变量环境和词法环境中,随后这些内容会随着执行上下文一起被压入调用栈中,然后依据栈的后进先出原则将所有代码全部执行。
点个赞再走吧~