本文深入解析JavaScript中的内存分配原理,重点区分原始值与引用值的存储差异。原始值(如Undefined、Number等)直接存储在栈内存中,而引用值(如Object、Array)的指针存于栈,实际对象位于堆内存。通过栈与堆的特性对比,阐明为何复杂对象需利用堆空间以平衡时间与空间效率,帮助开发者理解变量访问与内存管理的底层逻辑。
原始值与引用值的分类
在ECMAScript规范中,变量主要存储两类数据:原始值(Primitive Values)和引用值(Reference Values)。
原始值对应基本数据类型,具体包括Undefined、Null、Number、String和Boolean这五种类型所表示的数据。
引用值则对应复合数据类型,涵盖Object、Function、Array以及用户自定义的对象实例等。
栈和堆
栈与堆的内存结构
与上述两类数据相对应,JavaScript引擎使用两种不同的内存结构来存储它们:栈(Stack)和堆(Heap)。
栈是一种遵循后进先出(LIFO)原则的数据结构,在JavaScript中,我们可以利用Array数组来模拟栈的压入和弹出行为。
具体实现示例如下:
var arr = []; //创建一个栈
arr.push("apple");//压入元素"apple" ["apple"]
arr.push("orange");//压入元素"orange" ["apple","orange"]
arr.pop();//弹出"orange" ["apple"]
arr.push("banana");//压入元素"banana" ["apple","banana"] 结合上述栈的操作逻辑,我们可以直观地理解内存中数据的存取方式,如下图所示:

原始值作为简单数据段直接存储在栈内存中,这意味着变量的值直接存储在变量名所指向的内存地址位置。
堆内存是一种基于散列算法的数据结构,用于存储复杂数据;在JavaScript中,引用值的具体内容均存放于堆内存中。
对于引用值,变量在栈中存储的并非对象本身,而是一个指向堆内存中实际对象的指针(引用)。
例如执行var obj = new Object();时,变量obj本身存储在栈中,它包含一个指针,指向堆内存中new Object()创建的实际对象。
内存分配策略与性能权衡
既然栈和堆都是内存的一部分,为何要将原始值放在栈中,而将引用值放在堆中,而不是统一存储?这涉及到底层性能与结构的权衡,下文将深入探讨。
为了更清晰地展示内存分布,我们分析以下包含多种数据类型的代码示例:
代码实现如下:
function Person(id,name,age){
this.id = id;
this.name = name;
this.age = age;
}
var num = 10;
var bol = true;
var str = "abc";
var obj = new Object();
var arr = ['a','b','c'];
var person = new Person(100,"jxl",22); 对应的内存分配结构如下图所示:

变量num、bol、str属于基本数据类型,其值直接存储在栈内存中;而obj、person、arr属于复合数据类型,它们的变量名存储在栈中,但通过指针指向堆内存中实际存储的对象。
从内存图可以看出,我们无法直接访问或操作堆内存中的具体数据,这意味着不能直接操作对象实体;但我们可以通过栈中存储的引用来间接操作对象,这类似于通过遥控器控制电视机,区别在于堆中的对象本身没有直接的控制接口。
基于上述机制,我们可以回答为何原始值存栈、引用值存堆的核心原因:
核心原则可以概括为:系统资源守恒,本质上是时间换空间或空间换时间的权衡。
堆内存容量通常远大于栈内存,但栈的运算速度显著快于堆。对象结构复杂且可动态扩展(如数组无限增长、对象属性自由添加),若将此类复杂结构置于栈中,会严重影响栈的运行效率。因此,将对象置于堆中,仅通过栈中的引用进行查找和操作,是更优策略。相比之下,原始数据类型结构稳定且占用内存极小,若将其置于堆中并通过引用查找,其额外开销远大于直接从栈中获取值的成本。因此,原始值直接存储在栈中是效率最高的选择。
综上所述:
虽然内存模型看似简单,但它是程序运行的基石。基础概念至关重要,正如摩天大楼由一块块砖瓦搭建而成,理解内存是理解JavaScript的关键。
内存管理是程序执行的核心,透彻理解内存分配机制,有助于掌握JavaScript的本质。
本文旨在梳理核心概念,希望能为读者提供清晰的指引,共同进步。
参考资料:
《JavaScript高级程序设计》