JavaScript 执行环境、调用栈与作用域链
一、调用栈与执行环境
调用栈(Call Stack)
- 是 JavaScript 引擎用于管理函数执行顺序的结构。
- 先进后出:最后进入栈的函数最先被执行完并弹出。
let name = "Mike";
function fn() {
let name = "Alan";
fn2();
function fn2() {
console.log(name);
}
}
fn(); // Alan
执行顺序(调用栈):
全局环境 -> fn() -> fn2()
执行环境(Execution Context)
执行环境是一个对象「执行环境不是作用域」,存储各种信息「局部变量&作用域&参数&...」
执行环境有两部分:全局环境&局部环境
预编译阶段就是在进行执行环境的创建
首先创建环境->进栈->执行代码并赋值->执行完出栈
每当 JavaScript 执行一段代码时,都会创建对应的执行环境。
执行环境可以理解为一套记录函数运行状态的结构体,它包含:
- 局部变量
- 作用域链(外部环境的引用)
- 参数信息
- this 的指向
类型:
- 全局执行环境(程序启动时创建)
- 函数执行环境(函数调用时创建)
二、执行环境的内部结构
每一个执行环境都包含两个部分:
| 区域 | 内容 |
|---|---|
| 全局环境 | 内建对象、宿主对象、全局变量 |
| 局部环境 | 局部变量、外部环境引用 |
示意图:
执行环境
├─ 全局环境
│ ├─ 内建对象
│ ├─ 宿主对象
│ └─ 全局变量
└─ 局部环境
├─ 局部变量
└─ 外部环境
注意:全局执行环境中,局部环境与全局环境“合并”,因为它没有上级外部环境。
三、作用域与作用域链
作用域(Scope)
变量和函数的可访问范围。
- 全局作用域:程序中任意位置都能访问的变量。
- 局部作用域:函数内部定义的变量。
- 块级作用域:由
{}限定(let / const)。
作用域链(Scope Chain)
当访问变量时,JavaScript 会沿着作用域链向上查找:
- 当前函数作用域(局部变量)
- 外部函数作用域
- 全局作用域
四、作用域与调用栈的区别
来看两个例子👇
例 1:普通调用
let name = "Mike";
function fn() {
let name = "Alan";
fn2();
function fn2() {
console.log(name);
}
}
fn(); // Alan
调用栈顺序:
全局 -> fn() -> fn2()
fn2() 可以访问 fn() 的变量,是因为 fn2 在创建时就“记录”了外部作用域(词法作用域),与调用栈位置无关。
例 2:闭包情况
let name = "Mike";
function fn() {
let name = "Alan";
return function fn2() {
console.log(name);
};
}
const fn2 = fn();
fn2(); // Alan
调用栈顺序:
全局 -> fn() [执行完出栈] -> fn2()
虽然 fn() 已经执行完毕并出栈,但 fn2() 依然能访问 fn() 中的变量。
这说明:
作用域在函数创建时就已确定,不会因调用顺序而改变。
五、闭包与外部环境
- 每个执行环境在创建时,都会保存其外部环境引用(outer environment reference) 。
- 这条引用链组成了作用域链。
- 当函数被返回(形成闭包)时,其外部环境引用依旧保存在内存中。
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const add = outer();
add(); // 1
add(); // 2
inner() 形成闭包,持有对 outer() 的外部环境引用,因此能持续访问 count。
六、全局与局部执行环境创建过程
以以下代码为例:
let name = "Mike";
function fn() {
let name = "Alan";
return function fn2() {
console.log(name);
};
}
const fn2 = fn();
fn2(); // Alan
执行阶段分为两步:
-
创建全局执行环境
- 建立全局对象、全局变量。
- 记录函数声明(存入全局变量对象)。
-
函数执行时
- 创建函数执行环境(包括局部变量与外部引用)。
- 形成新的作用域链。
函数查找变量的顺序:
局部变量 → 外部环境变量 → 全局变量
七、总结
| 概念 | 功能 | 说明 |
|---|---|---|
| 调用栈 | 管理函数执行顺序 | 先进后出 |
| 执行环境 | 存储函数运行时信息 | 包含变量、作用域、this |
| 作用域 | 控制变量可见性 | 函数创建时决定 |
| 作用域链 | 变量查找路径 | 向外层逐级查找 |
| 闭包 | 保留外部环境引用 | 实现数据持久化 |