JavaScript 执行机制深度解析:编译、执行上下文、变量提升、TDZ 与内存模型

彻底读懂 JavaScript 执行机制:编译、执行上下文、变量提升、TDZ 与内存模型全解析

很多初学者在学习 JavaScript 时都会遇到这些常见问题:

  • 为什么变量打印出来是 undefined
  • 为什么访问 letconst 会抛出 ReferenceError
  • 为什么函数声明可以提前调用,而函数表达式不行?
  • 为什么两个对象会"联动修改"?
  • 为什么形参与 var 同名时行为异常?

这些看似独立的问题,其实都源于同一个核心原理:
JavaScript 的执行机制 ------ "先编译,后执行"

理解这一点,你就真正掌握了 JS 的底层运行逻辑。


一、JS 运行机制:编译阶段与执行阶段

JavaScript 属于"解释型 + 编译优化"的语言,执行流程分为两个阶段:

1. 编译阶段(Creation Phase)

在编译阶段,V8 会进行以下操作:

  • 创建执行上下文(Execution Context)
  • 处理 var 变量提升
  • 处理函数声明提升
  • 注册 let / const(但不初始化,进入暂时性死区 TDZ)

编译阶段就是 JS 在执行前"扫一遍",把变量、函数和作用域信息先登记好。

2. 执行阶段(Execution Phase)

逐行执行代码,变量赋值、函数调用在此阶段完成。

  • 这解释了为什么 var 变量会被提升为 undefined
  • 以及为什么访问 let / const 会触发 TDZ

二、执行上下文(Execution Context)

每段代码的执行都有一个"容器",即执行上下文对象(Execution Context),它包含三个主要部分:

1. 变量环境(Variable Environment)

  • 存放 var 声明(初始化为 undefined
  • 存放函数声明(直接绑定函数体)

2. 词法环境(Lexical Environment)

  • 存放 let / const 声明(仅注册,不初始化)
  • 存放块级作用域变量
  • TDZ(暂时性死区)就发生在这里

3. this 绑定

  • 决定 this 指向
  • 全局上下文、函数上下文、箭头函数各自规则不同

理解执行上下文,就能解释变量提升、函数调用顺序以及 TDZ 的行为。


三、调用栈(Call Stack)

JS 以"函数"为单位执行代码:

  1. 全局执行上下文压入栈底
  2. 调用函数 → 创建函数执行上下文 → 入栈
  3. 函数执行完 → 出栈并销毁
  4. 所有代码执行完 → 全局上下文退出

JS 执行机制的"栈结构"保证了函数调用顺序和作用域的正确管理。


四、核心示例解析

示例 1:函数声明提升 + TDZ

ini 复制代码
showName();
console.log(myName);
console.log(hero);

var myName = 'ouma_syu';
let hero = '钢铁侠';

function showName() {
    console.log(myName);
    console.log('函数showName被执行');
}

编译阶段:

  • 变量环境:
名称
showName 函数体
myName undefined
  • 词法环境:
名称 状态
hero TDZ(未初始化)

执行阶段输出:

javascript 复制代码
undefined
函数showName被执行
undefined
ReferenceError: Cannot access 'hero' before initialization

要点:

  • 函数声明提升优先级最高,可提前调用
  • var 提升为 undefined
  • let/const 进入 TDZ,访问报错

示例 2:变量提升本质

ini 复制代码
var myName;
function showName() {
    console.log('函数showName被');
}
showName();
console.log(myName);
myName = 'ouma_syu';

提升后的逻辑等价于:

javascript 复制代码
var myName = undefined;
function showName() {...}

JS 提前知道当前作用域有哪些变量与函数,保证"先调用后定义"可行。


示例 3:形参与 var 冲突

ini 复制代码
var a = 1;
function fn(a) {
    console.log(a);
    var a = 2;
    var b = a;
    console.log(a);
}
fn(3);
fn(3);
console.log(a);

执行结果:

复制代码
3
2
3
2
1

解析:

  • 形参优先于 var 声明
  • 函数内部变量与全局变量独立
  • var 重复声明会被忽略

示例 4:var 与 let 差异

ini 复制代码
console.log(a); // undefined
console.log(b); // ReferenceError

var a = 1;
console.log(a); // 1

let b = 3;
console.log(b); // 3
  • var 提升并初始化为 undefined
  • let 提升但未初始化,访问报错

示例 5:函数表达式不提升

php 复制代码
fn();  // ReferenceError

let fn = () => {
    console.log('函数表达式不会提升');
}
  • let 声明在 TDZ 中无法访问
  • 即使 var fn,调用也会报 TypeError
  • 函数表达式本质是变量,不像函数声明完全提升

示例 6:基本类型 vs 引用类型

基本类型(值存储,栈内存)

ini 复制代码
let str = 'hello';
let str2 = str;

str2 = '你好';
console.log(str, str2); // hello 你好

引用类型(地址存储,堆内存)

ini 复制代码
let obj = { name:'ouma_syu', age:18 };
let obj2 = obj;

obj.age++;
console.log(obj, obj2); // {age:19}, {age:19}

基本类型复制的是值,引用类型复制的是地址。修改引用类型时,所有指向该地址的变量都会受到影响。


五、JS 执行机制最终总结

  1. 先编译,后执行
  2. 执行上下文 = 变量环境 + 词法环境 + this
  3. 提升规则是理解 JS 行为的基础
类型 是否提升 初始化状态 可提前访问 访问行为 作用域 注意事项
函数声明 完全提升 函数体已准备好 可提前调用 正常调用 当前上下文 优先级最高,覆盖同名 var
var 声明提升 undefined 可访问 undefined,赋值后生效 函数或全局 可重复声明,不会报错
let 注册提升 未初始化(TDZ) 不可访问 ReferenceError 块级作用域 声明后使用;进入 TDZ 直到执行
const 注册提升 未初始化(TDZ) 不可访问 ReferenceError 块级作用域 声明时必须初始化;不可重复赋值
  1. 调用栈保证以函数为单位执行
  2. 基本类型存值,引用类型存地址

掌握这些底层机制后,JS 中的大部分"奇怪行为"都能预测和理解。

相关推荐
金梦人生2 小时前
UniApp + Vue3 + TS 工程化实战笔记
前端·微信小程序
海云前端12 小时前
20 个浏览器原生能力 替代工具库少写百行代码
前端
Holin_浩霖2 小时前
🌿 Fiber 异步渲染机制 & 时间切片原理详解
前端
烟袅2 小时前
深入浏览器渲染流程:从 HTML/CSS/JS 到 60FPS 的视觉魔法
前端·css·html
_一两风2 小时前
深入理解JavaScript执行机制:从一道经典面试题说起
javascript
阿凡达蘑菇灯2 小时前
langgraph---条件边
开发语言·前端·javascript
San302 小时前
深入理解浏览器渲染流程:从HTML/CSS到像素的奇妙旅程
javascript·css·html
海云前端12 小时前
别再堆 if-else 了!TypeScript 模式匹配让代码更优雅
前端
拖拉斯旋风2 小时前
深入理解 JavaScript 执行机制之V8引擎:从编译到执行的完整生命周期
javascript·面试