JavaScript 是怎么“跑”起来的?一文搞懂 JS 的执行机制与内存管理

40 阅读3分钟

“你写的代码,真的按顺序执行了吗?”

今天不聊框架、不卷算法,来聊聊 JavaScript 底层那些“看不见”的事儿——JS 的执行机制和内存机制


🚀 一段看似简单的 JS 代码,背后发生了什么?

先来看这段经典代码:

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

咦?明明 a 还没赋值,为啥不是报错,而是 undefined
再看这个:

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;

同样是“提前使用”,let 就直接翻脸了?😤

这背后,就是 JavaScript 引擎(比如 Chrome 的 V8) 在“暗中操作”!而它的核心机制,离不开两个阶段:编译阶段执行阶段


🔧 阶段一:编译阶段(发生在“执行前的一刹那”)

很多人以为 JS 是纯解释型语言,写完就跑。其实不然!现代 JS 引擎(如 V8)采用的是 “即时编译”(JIT) 技术——边编译、边优化、再执行

在真正执行代码前,V8 会先快速扫一遍代码,干几件重要的事:

✅ 1. 创建执行上下文(Execution Context)

每一段可执行代码(全局 or 函数),都会被包裹在一个 执行上下文对象 中。你可以把它想象成一个“任务包”,里面装着:

  • 变量环境(Variable Environment) :存放 var 声明的变量、函数声明。
  • 词法环境(Lexical Environment) :存放 let/const 声明的变量。
  • this 指向
  • 作用域链

💡 小知识:全局代码 → 全局执行上下文;函数调用 → 函数执行上下文。

✅ 2. 变量提升(Hoisting)——但 var 和 let/const 不一样!

  • var:在编译阶段就被“提升”到变量环境中,初始值为 undefined
  • let / const:也会被提升,但不会初始化!它们处于“暂时性死区”(Temporal Dead Zone, TDZ),直到赋值那一刻才能访问。

所以:

console.log(a); // undefined(var 提升了)
var a = 1;

console.log(b); // ❌ 报错!
let b = 2;

🏃 阶段二:执行阶段(调用栈登场!)

编译完成后,JS 开始逐行执行。这时,调用栈(Call Stack) 成了主角!

📦 调用栈:JS 执行的“任务管理器”

  • JS 是单线程的,靠 栈结构 管理函数调用。
  • 全局上下文 最先入栈。
  • 每调用一个函数,就创建新的执行上下文,压入栈顶
  • 函数执行完,上下文弹出栈,里面的局部变量随之销毁(后续被垃圾回收)。

举个栗子 🌰:

function fun(a) {
  var b = a;
  a = 2;
  console.log(a, b); // 2, 3
}
fun(3);

执行过程:

  1. 全局上下文入栈。

  2. 调用 fun(3) → 创建函数上下文,压入栈顶。

    • 编译阶段:a(形参)= 3,b = undefined(var 提升)
    • 执行阶段:b = a → b = 3;a = 2 → a 变成 2
  3. 函数执行完,上下文弹出,ab 消失。

🗑️ 内存提示:函数上下文销毁 ≠ 内存立刻释放!V8 的垃圾回收器(GC)会在合适时机清理无引用的对象。


🆚 var vs let/const:不只是语法糖!

特性varlet / const
提升✅ 到变量环境,值为 undefined✅ 到词法环境,但处于 TDZ
重复声明✅ 允许❌ 报错
作用域函数级块级({})
全局污染会挂到 window不会

所以,现代开发请优先用 let/const!更安全、更可控。


🧩 总结:JS 执行的“三步走”

  1. 编译阶段(快如闪电⚡)

    • 创建执行上下文
    • 变量/函数提升(区分 var 和 let/const)
    • 构建作用域链
  2. 执行阶段(按序推进▶️)

    • 代码逐行运行
    • 函数调用 → 新上下文入栈
    • 执行完毕 → 上下文出栈
  3. 内存回收(默默善后🧹)

    • 栈中上下文销毁
    • 堆中无引用对象 → GC 回收

💬 最后说两句

JavaScript 看似简单,但它的执行机制藏着不少“小心机”。理解这些底层逻辑,不仅能帮你避开 undefined 的坑,还能在面试时自信地说:“我知道 V8 是怎么跑我代码的!” 😎

下次当你看到 Cannot access 'x' before initialization,别慌——那只是 let 在保护你,免得写出 bug 啊!