一、核心概念速览
| 概念 | 含义 |
|---|---|
| ECStack | 执行上下文栈,遵循“先进后出”原则,管理所有EC的生命周期(全局EC压栈→函数EC压栈→执行完出栈)。 |
| EC | 执行上下文,代码执行的“环境容器”,包含:1. VO/AO(变量存储)2. 作用域链(变量查找)>3. this绑定 |
| VO | 变量对象,全局/全局函数的变量存储容器,对应全局对象GO。 |
| AO | 活动对象,函数执行时的变量对象(VO的分支),包含参数、arguments、局部变量/函数。 |
| GO | 全局对象,浏览器中对应window,存储所有全局变量/函数,是全局VO的载体。 |
| 作用域链 | 由当前EC的AO/VO + 外层EC的作用域链组成,用于变量查找(从当前上下文向外层逐级查找)。 |
二、经典题目解析:底层执行逻辑
题目1:引用类型的赋值与优先级
let a = { n: 10 };
let b = a;
// 运算优先级:同等级从左到右
b.m = b = { n: 20 };
console.log(a); // {n:10, m:{n:20}}
console.log(b); // {n:20}
底层逻辑:
初始化阶段:
- 创建对象
{n:10},堆内存地址为AAAfff000; a和b在全局VO中存储地址AAAfff000,均指向该对象。
- 创建对象
执行
b.m = b = {n:20}:- 运算顺序:先处理左侧
b.m(此时b仍指向AAAfff000),再执行赋值; - 步骤1:创建新对象
{n:20},堆地址为AAAfff111; - 步骤2:
b = {n:20}→b的地址改为AAAfff111; - 步骤3:
b.m = {n:20}→ 给原对象(AAAfff000)添加属性m,值为AAAfff111。
- 运算顺序:先处理左侧
最终结果:
a仍指向AAAfff000→ 输出{n:10, m:{n:20}};b指向AAAfff111→ 输出{n:20}。
题目2:函数参数的引用与重新赋值
let x = [12, 23]; // 堆地址AAAfff000
function fn(y) {
y[0] = 100; // 修改y指向的数组(x的地址)
y = [100]; // y重新赋值为新数组(地址BBBfff000)
y[1] = 200; // 修改新数组
console.log(y); // [100, 200]
}
fn(x);
console.log(x); // [100, 23]
底层逻辑:
函数执行前:
- 全局VO中
x存储数组地址AAAfff000。
- 全局VO中
函数执行(创建EC(FN)):
- AO中
y接收参数x的地址AAAfff000; y[0] = 100→ 修改AAAfff000数组的第0项为100;y = [100]→y的地址改为BBBfff000(与x无关);y[1] = 200→ 修改新数组BBBfff000,输出[100, 200]。
- AO中
函数执行后:
x仍指向AAAfff000→ 输出[100, 23]。
题目3:自执行函数的变量与运算符优先级
var x = 10; // 全局VO中x=10
~function (x) { // 自执行函数,形参x覆盖全局x
console.log(x); // undefined(形参x未赋值)
// &&优先级高于||,先算20&&30=30,再算x||30||40
x = x || 20 && 30 || 40;
console.log(x); // 30
}();
console.log(x); // 10(全局x未被修改)
底层逻辑:
自执行函数执行(创建EC):
- AO中形参
x未传递实参 → 初始值为undefined; - 第一个
console.log(x)→ 输出undefined; - 运算
20&&30=30,再执行x || 30→undefined || 30 = 30,最终x=30; - 第二个
console.log(x)→ 输出30。
- AO中形参
函数执行后:
- 全局VO中
x仍为10 → 输出10。
- 全局VO中
三、核心结论
- VO/AO是变量的“存储容器”:全局变量存在GO(VO),函数变量存在AO;
- 引用类型赋值传递的是地址:多个变量可指向同一堆内存,修改会同步(重新赋值除外);
- 作用域链决定变量查找范围:函数内变量优先从自身AO查找,再向外层VO查找;
- 运算符优先级/执行顺序:会直接影响引用类型的操作对象(如题目1中
b.m的指向)。
