深入理解JavaScript内存空间:栈、堆与垃圾回收机制
本文深入解析JavaScript的内存管理机制,区分栈内存与堆内存的作用及数据存储方式。详细阐述基础类型按值访问与引用类型按引用访问的区别,并通过代码示例说明变量复制时的内存行为。最后介绍JavaScript的自动垃圾收集机制,包括标记清除算法及手动解除引用的最佳实践,帮助开发者优化代码性能。
概述
变量对象与堆内存
var a = 20;
var b = 'abc';
var c = true;
var d = { m: 20 }

过去,许多开发者认为内存空间概念在JavaScript学习中并非核心重点。然而,当我重新梳理JS基础知识时,发现对内存机制的模糊认知导致了对诸多核心概念的理解偏差,例如引用数据类型与引用传递的本质、浅拷贝与深拷贝的区别,以及闭包和原型链的工作原理。
因此,要实现对JavaScript更深刻的理解,建立清晰的内存空间认知是必经之路。
一、栈与堆
注:栈,也可以叫堆栈
与C/C++等语言不同,JavaScript并没有在严格层面上区分栈内存与堆内存。我们可以粗略地将JavaScript的所有数据存储视为堆内存的一部分。但在某些特定场景下,仍需借助堆栈数据结构的逻辑进行处理,例如JavaScript的执行上下文(执行上下文的详细总结将在后续文章中发布)。执行上下文在逻辑层面实现了堆栈结构,因此理解堆栈数据结构的原理与特性依然至关重要。
为了直观理解栈的存取方式,我们可以通过类比乒乓球盒来观察。见下图左侧部分。

乒乓球盒子与栈类比
这种乒乓球的存放方式与栈中存取数据的方式完全一致。位于盒子最顶层的乒乓球5,它是最后放入的,但却是第一个被取用的。若想取出底层的乒乓球1,必须先取出上面的4个球,使乒乓球1处于顶层。这体现了栈空间“先进后出,后进先出”的特点。图中已详细展示了栈空间的存储原理。
堆存取数据的方式,则与书架和书籍的关系非常相似。
虽然书籍整齐地排列在书架上,但只要知道书名,就能方便地取出想要的书,无需像从乒乓球盒中取球那样,必须移开上面的所有球才能拿到中间的某一本。这好比JSON格式数据中存储的key-value对是无序的,因为顺序不影响使用,我们只需关注键名即可。
二、变量对象与基础数据类型
JavaScript执行上下文生成后,会创建一个名为变量对象(Variable Object)的特殊对象(具体细节将在后续文章结合执行上下文总结)。JavaScript的基础数据类型通常保存在变量对象中。
严格来说,变量对象本身也存放于堆内存中,但由于其特殊职能,我们在理解时仍需将其与普通的堆内存区分开来。
基础数据类型是一些简单的数据段,JavaScript中共有5种基础数据类型:Undefined、Null、Boolean、Number、String。基础数据类型按值访问,因为我们可以直接操作保存在变量中的实际值。
三、引用数据类型与堆内存
与其他语言不同,JavaScript的引用数据类型(如数组Array)其值的大小是不固定的。引用数据类型的值保存在堆内存中的对象里。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而非实际对象。因此,引用类型的值都是按引用访问的。这里的“引用”,可以粗略理解为保存在变量对象中的一个地址,该地址与堆内存中的实际值相关联。
为了更好地理解变量对象与堆内存的关系,我们可以结合以下例子与图解进行分析。
var a1 = 0; // 变量对象
var a2 = 'this is string'; // 变量对象
var a3 = null; // 变量对象
var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中

上例图解
因此,当我们要访问堆内存中的引用数据类型时,实际上首先是从变量对象中获取该对象的地址引用(或地址指针),然后再从堆内存中取得所需的数据。
理解了JS的内存空间,我们就可以借助内存空间的特性来验证引用类型的一些特点。
在前端面试中,我们常常会遇到类似的题目。
// demo01.js
var a = 20;
var b = a;
b = 30;
// 这时a的值是多少?
// demo02.js
var m = { a: 10, b: 20 }
var n = m;
n.a = 15;
// 这时m.a的值是多少
当变量对象中的数据发生复制行为时,系统会自动为新的变量分配一个新值。执行`var b = a`后,虽然a与b的值都等于20,但它们已成为相互独立、互不影响的值。具体见图。因此,修改b的值后,a的值不会发生变化。

demo01图解
在demo02中,通过`var n = m`执行一次复制引用类型的操作。引用类型的复制同样会为新的变量自动分配一个新的值保存在变量对象中,但不同的是,这个新值仅仅是引用类型的一个地址指针。当地址指针相同时,尽管它们在变量对象中相互独立,但在变量对象中访问到的具体对象实际上是同一个。如图所示。
因此,当我改变n时,m也随之发生变化。这就是引用类型的特性。

demo02图解
通过内存角度来理解,是否感觉轻松许多?除此之外,我们还可以以此为基础,逐步理解JavaScript的执行上下文、作用域链、闭包、原型链等重要概念。其他内容将在后续文章中慢慢总结,敬请期待。
内存空间管理
由于JavaScript具有自动垃圾收集机制,开发时似乎无需关心内存使用问题,内存的分配与回收完全实现了自动管理。但根据我的开发经验,了解内存机制有助于清晰认识代码执行过程中的内存变化,从而编写出性能更优的代码。因此,关注内存管理是一件非常重要的事情。
JavaScript的内存生命周期
1. 分配你所需要的内存
2. 使用分配到的内存(读、写)
3. 不需要时将其释放、归还
为了便于理解,我们使用一个简单的例子来解释这个周期。
var a = 20; // 在内存中给数值变量分配空间
alert(a + 100); // 使用内存
var a = null; // 使用完毕之后,释放内存空间
第一步和第二步我们都很好理解,JavaScript在定义变量时就完成了内存分配。第三步释放内存空间则是我们需要重点理解的一个点。
JavaScript有自动垃圾收集机制,那么这个自动垃圾收集机制的原理是什么呢?其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。
在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。
在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量,以确保性能问题。要详细了解垃圾收集机制,建议阅读《JavaScript高级编程》中的4.3节
以上就是详解JS内存空间的详细内容,更多关于JS内存空间的资料请关注本站其它相关文章!
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
