JavaScript 的代码执行机制宛如一台精密的机器,而执行上下文 和调用栈 正是这台机器的核心齿轮。大家好,我是kada,这篇文章将带您深入了解执行上下文 、词法环境 、变量环境 、调用栈 、作用域之间的交互体验。
一、执行上下文:JavaScript 的代码执行单元
1. 执行上下文的类型
- 全局执行上下文:代码的起点,创建全局对象(浏览器中为 window)
- 函数执行上下文:每次函数调用时动态创建
- Eval 执行上下文(较少使用)
2. 执行上下文的生命周期
每个执行上下文经历两个阶段:
- 创建阶段(编译时):
- 创建变量环境(VariableEnvironment):存储 var 声明和函数声明
- 创建词法环境(LexicalEnvironment):存储 let/const 声明
- 建立 outer 引用(作用域链)
- 确定 this 绑定
- 执行阶段 (运行时):
按顺序执行代码,进行赋值操作
二、关键组件解析
1. 变量环境 vs 词法环境
-
变量环境(Variable Environment) :
-
存储
var
声明和函数声明 -
在函数/全局作用域初始化时完全创建
-
表现为对象环境记录(Object Environment Record)
javascript// 伪代码实现 variableEnvironment = { a: undefined, c: undefined // 块内的 var 同样提升到函数作用域 }
-
-
词法环境(Lexical Environment) :
-
存储
let/const
声明 -
采用声明式环境记录(Declarative Environment Record)
-
具有层级结构,每个代码块创建新环境
yaml// 伪代码结构 lexEnv = { b: <uninitialized>, outer: globalEnv, // 块级环境 blockEnv: { d: <uninitialized>, outer: lexEnv } }
-
关键差异对比:
特性 | 变量环境(var) | 词法环境(let/const) |
---|---|---|
初始化时机 | 执行上下文创建时设为 undefined | 进入作用域时保持未初始化 |
内存分配 | 编译阶段分配固定空间 | 动态创建层级环境 |
块级作用域支持 | 无(渗透到函数作用域) | 通过嵌套环境实现 |
删除属性 | 可删除(window 对象) | 不可删除 |
暂存死区(TDZ) | 不存在 | 声明前访问会抛出引用错误 |
经典案例解析:
ini
console.log(a); // undefined
var a = 10;
console.log(b); //报错
let b = 20;
2. 作用域链的形成
javascript
function outer() {
const a = 10;
function inner() {
console.log(a); // 通过 outer 引用链查找
}
return inner;
}
在这个例子中,当我们调用 outer()
函数并获取返回的 inner
函数时,即使在 outer
函数执行完毕后,inner
函数仍然能访问变量 a
。这是因为当创建 inner
函数时,JavaScript引擎不仅创建了函数对象本身,还创建了一个闭包(closure),该闭包包含了对 outer
函数的执行上下文的引用。
具体来说,当 inner
函数被调用时,JavaScript引擎首先会在 inner
函数的作用域内查找变量 a
。如果找不到,则会沿着作用域链向上查找,直到找到或者到达全局作用域为止。在这种情况下,由于 a
不是在 inner
函数内部定义的,所以JavaScript引擎会通过 inner
函数的闭包找到 outer
函数的作用域,并最终在那里找到变量 a
。
三、调用栈的运行机制
让我们通过一个具体的例子来展示调用栈和执行上下文是如何工作的:
ini
javascript
function first() {
const a = 'First';
second();
console.log(a);
}
function second() {
const b = 'Second';
third();
console.log(b);
}
function third() {
const c = 'Third';
console.log(c);
}
first();
调用栈的变化流程如下:
-
初始状态:
[全局执行上下文]
-
调用
first()
后:[全局执行上下文, first()执行上下文]
- 创建
a = 'First'
- 调用
second()
- 创建
-
调用
second()
后:[全局执行上下文, first()执行上下文, second()执行上下文]
- 创建
b = 'Second'
- 调用
third()
- 创建
-
调用
third()
后:[全局执行上下文, first()执行上下文, second()执行上下文, third()执行上下文]
- 创建
c = 'Third'
- 打印
c
输出'Third'
- 创建
-
third()
执行完毕并从栈中弹出:[全局执行上下文, first()执行上下文, second()执行上下文]
-
在
second()
中打印b
输出'Second'
-
second()
执行完毕并从栈中弹出:[全局执行上下文, first()执行上下文]
-
在
first()
中打印a
输出'First'
-
first()
执行完毕并从栈中弹出:[全局执行上下文]
输出结果依次为:
sql
Third
Second
First
在这个过程中,我们可以看到:
- 每个函数调用都会创建一个新的执行上下文,并将其压入调用栈。
- 函数执行完毕后,对应的执行上下文会从调用栈中弹出。
- 通过作用域链,函数可以访问其定义时所在的作用域中的变量,即使是在不同的执行上下文中调用该函数。
调用栈和执行上下文是JavaScript引擎管理代码执行的核心机制。调用栈负责跟踪函数调用的顺序,而执行上下文则提供了每个函数调用所需的环境信息。两者紧密合作,确保了JavaScript代码能够按照预期的方式执行,特别是在处理作用域、闭包和异步编程等方面时。理解这两者的工作原理对于编写高效、可靠的JavaScript代码至关重要。
最后,感谢大家的阅读,希望这篇文章有益于你🚀