javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript堆内存(Heap)与栈内存(Stack)

JavaScript内存机制之堆内存(Heap)与栈内存(Stack)详解

作者:yqcoder

文章将JavaScript引擎中的内存分为栈和堆,详细描述了它们的特点、用途、管理方式和垃圾回收机制,通过代码实战演示了基本类型和引用类型的数据存储方式,并解释了闭包和内存泄漏的概念,总结了栈和堆的区别,提供了面试高频问题的回答方法,需要的朋友可以参考下

在 JavaScript 引擎(如 V8)中,内存主要分为两个区域:栈内存(Stack)堆内存(Heap)
它们就像公司的办公桌仓库,分工明确,协作高效。

1. 核心比喻:办公桌 vs 仓库

为了通俗易懂,我们把浏览器内存想象成一家公司:

栈(Stack) = 员工的办公桌

堆(Heap) = 公司的公共仓库

2. 栈(Stack):快速、有序、自动清理

存储内容

工作机制

  1. 分配:当声明一个变量或调用一个函数时,内存会自动在栈顶分配一块固定大小的空间。
  2. 释放:当函数执行完毕或变量超出作用域时,这块内存会自动弹出并释放。
  3. 速度:极快,因为不需要查找,直接操作栈顶指针。

注意:在 JS 中,短字符串有时也会存储在栈中(取决于引擎优化),但逻辑上我们将其视为值类型。

3. 堆(Heap):庞大、无序、手动回收

存储内容

工作机制

  1. 分配:当创建一个对象时,引擎会在堆中开辟一块空间存放数据。
  2. 引用:栈中只存储一个指针(内存地址),指向堆中的这块数据。
  3. 释放不会自动立即释放。需要依靠浏览器的垃圾回收机制(Garbage Collection, GC) 来定期扫描,找出不再被引用的对象并清除。

4. 代码实战:数据是如何存储的?

让我们通过代码看看栈和堆是如何协作的。

场景一:基本类型(全在栈中)

let a = 10;
let b = a; // 拷贝值
b = 20;

console.log(a); // 10
console.log(b); // 20

内存图解

Stack (栈)
+-------+
| b: 20 |  <-- 修改 b,不影响 a
+-------+
| a: 10 |  <-- a 保持原值
+-------+

结论:基本类型是值拷贝。互不影响。

场景二:引用类型(栈存地址,堆存数据)

let obj1 = { name: "Lingma" };
let obj2 = obj1; // 拷贝地址(指针)
obj2.name = "Aliyun";

console.log(obj1.name); // "Aliyun" 😱 obj1 也被改变了!
console.log(obj2.name); // "Aliyun"

内存图解

Stack (栈)                  Heap (堆)
+----------+              +------------------+
| obj1     |------------->| { name: "Aliyun" } |
+----------+              +------------------+
| obj2     |-------------^  (同一个对象)
+----------+

结论:引用类型是引用拷贝(浅拷贝)。obj1obj2 指向堆中的同一个对象。修改其中一个,另一个也会变。

5. 垃圾回收:谁在打扫战场?

既然堆内存不会自动释放,那什么时候清理呢?
浏览器使用 垃圾回收机制(GC)。主流算法是 标记-清除(Mark-and-Sweep)

工作流程

  1. 标记:GC 从根节点(如 window、全局变量)出发,遍历所有能访问到的对象,打上“存活”标记。
  2. 清除:遍历堆内存,那些没有被打上标记的对象,说明已经没有任何变量引用它们了,于是被判定为“垃圾”,占用内存被释放。

内存泄漏(Memory Leak)

如果代码中存在意外的全局变量未清理的定时器循环引用,导致某些对象永远无法被 GC 标记为“可回收”,内存就会越占越多,最终导致页面卡顿甚至崩溃。

常见泄漏场景

// 1. 意外全局变量
function leak() {
  leakedVar = "I am global now"; // 忘记写 let/var/const
}

// 2. 未清除的定时器
const timer = setInterval(() => {
  console.log("I never stop");
}, 1000);
// 如果不清除 clearInterval(timer),回调函数及其引用的变量永远不会被回收

6. 总结与面试考点

特性栈(Stack)堆(Heap)
存储内容基本类型、执行上下文引用类型(对象、数组等)
大小小,固定大小大,动态分配
存取速度
管理方式自动分配与释放(LIFO)垃圾回收机制(GC)
数据结构线性,有序非线性,无序
拷贝行为值拷贝(深拷贝效果)引用拷贝(浅拷贝)

面试高频问答

Q1: 为什么基本类型赋值互不影响,而对象赋值会相互影响?

A: 因为基本类型存在栈中,赋值是复制值;对象存在堆中,栈里只存地址,赋值是复制地址,两者指向同一块堆内存。

Q2: 什么是深拷贝?如何实现?

A: 深拷贝是在堆中开辟一块新内存,将原对象的所有层级数据完整复制一份。实现方式:JSON.parse(JSON.stringify(obj))(有局限)、递归复制、或使用 structuredClone API。

Q3: 闭包会导致内存泄漏吗?

A: 不一定。闭包会阻止外部变量被回收,这是正常现象。但如果闭包长期存在且引用了巨大的无用数据,且无法被 GC 回收,才会导致泄漏。

博主寄语
理解堆和栈,不仅仅是为了应付面试。
当你遇到“修改了一个对象,另一个莫名其妙也变了”的 Bug 时,你会想起堆内存的共享特性
当你发现页面越来越卡时,你会想起堆内存的垃圾回收

记住口诀
栈小快,存基本,自动清理不费力。
堆大慢,存对象,引用拷贝要注意。
垃圾回收靠标记,内存泄漏要警惕。

以上就是JavaScript内存之堆内存(Heap)与栈内存(Stack)详解的详细内容,更多关于JavaScript堆内存(Heap)与栈内存(Stack)的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文