home

堆栈内存、变量提升与闭包作用域

2025年4年10日 · 761

📦 一、JavaScript 的内存机制:堆与栈

JavaScript 在执行过程中会使用两种主要的内存空间:

✅ 栈内存(Stack)

  • 存储原始类型的值(如数字、字符串、布尔值等)
  • 管理代码的执行上下文(ECStack)

✅ 堆内存(Heap)

  • 存储对象类型(对象、数组、函数等)
  • 对象是通过引用地址来操作的
let a = { n: 1 };
let b = a;
a.x = a = { n: 2 };
console.log(a.x); // undefined
console.log(b);   // { n: 1, x: { n: 2 } }

console.log(fn);
function fn() { console.log(1); }

var fn = 12;

function fn() { console.log(2); }

console.log(fn); // 12

🚀 二、执行上下文与变量对象

每当一段代码被执行时,JS 引擎会创建一个“执行上下文”(Execution Context):

  • 全局上下文(EC(G)):页面加载时创建,永久存在
  • 函数上下文(EC(fn)):函数调用时创建,用完即销毁
  • 块级上下文(EC(block)):在代码块中使用 let/const/function/class 时创建

执行过程:

  1. 创建变量对象(VO 或 AO)
  2. 变量提升
  3. 进入执行栈,逐句执行
  4. 执行完毕后,视情况出栈或保留(闭包)

📌 三、变量声明与预处理机制(变量提升)

在“当前上下文”中,代码执行之前,浏览器首先会把所有带var/function关键字的进行提前声明或者定义:带var的只是提前声明 & 带function的,此阶段声明+定义 "赋值" 都完成了

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

console.log(b); // ReferenceError
let b = 12;

条件语句中的函数声明在变量提升阶段,只声明不赋值,行为不一致,因此推荐使用函数表达式。


🧱 四、块级作用域与私有上下文

除“函数和对象”的大括号外「例如:判断体/循环体/代码块…」,如果在大括号中出现了 let/const/function/class 等关键词声明变量,则当前大括号会产生一个“块级私有上下文”;它的上级上下文是所处的环境;var不产生,也不受块级上下文的影响;

if (true) {
  let x = 10;
  const y = 20;
  function test() {}
}

注意事项:

  • var 声明不会被限制在块级作用域中
  • 函数声明在块中表现不一致,被戏称为“函数是个渣男”

🧠 五、闭包与垃圾回收机制(GC)

✅ 什么是闭包?

当函数内部返回另一个函数,并且内部函数引用了外部函数的变量,就形成了闭包。

function fn(x) {
  return function(y) {
    console.log(y + (++x));
  }
}

let f = fn(6);
f(7);         // 输出 14
fn(8)(9);     // 输出 18
f(10);        // 输出 17

✅ 为什么闭包会“记住”变量?

当一个函数返回时,它的执行上下文通常会被销毁。但如果有外部变量仍然引用它(比如闭包函数被赋值给了一个变量),它就不会被销毁。

✅ 垃圾回收机制

  • 标记清除(Mark & Sweep)
  • 引用计数(Reference Counting)

如果某段内存仍然被引用,它就不会被释放。这就是闭包能“保护”变量的原理。