堆栈内存、变量提升与闭包作用域
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
时创建
执行过程:
- 创建变量对象(VO 或 AO)
- 变量提升
- 进入执行栈,逐句执行
- 执行完毕后,视情况出栈或保留(闭包)
📌 三、变量声明与预处理机制(变量提升)
在“当前上下文”中,代码执行之前,浏览器首先会把所有带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)
如果某段内存仍然被引用,它就不会被释放。这就是闭包能“保护”变量的原理。