JavaScript基本数据类型与引用类型在内存存储上有什么区别

作者:向量旅人时间:2026-10-30 18:56:24浏览:505

深入解析JavaScript基本数据类型与引用类型在栈内存与堆内存中的存储机制差异,结合变量复制、函数传参及修改影响,提供清晰的图文对照与实践指南。

在 JavaScript 中,数据类型被划分为两大类:基本数据类型(Primitive Types)和引用数据类型(Reference Types)。它们在内存存储上的核心区别可以概括为一句话:基本数据类型的值大小固定,直接按值保存在栈内存(Stack)中;引用数据类型的值大小动态且结构复杂,实体保存在堆内存(Heap)中,而栈内存中仅保存指向该堆内存地址的引用指针。

一、栈内存与堆内存的基本分工

为了理解这两种类型的存储差异,首先需要明白 JavaScript 运行环境中的两块主要内存区域的作用机制:

  • 栈内存(Stack):由系统自动分配和释放,具有“后进先出(LIFO)”的高效线性数据结构。栈内存的访问速度极快,但空间相对有限,因此只适合存放生命周期明确、占用空间大小固定的数据,例如当前执行上下文中的局部变量标识符、基本类型的值以及对象的内存地址指针。
  • 堆内存(Heap):是一块动态分配、结构不受限制的无序内存池。堆内存空间大,可以存储结构多变、属性可随时增删的对象实体。但由于需要通过指针进行寻址分配和垃圾回收清理,访问速度相比栈内存稍慢。
JavaScript基本数据类型与引用类型在内存存储上有什么区别
图 1:基本类型直接内嵌在栈内存,引用类型的栈变量仅持有堆内存寻址指针

二、两类数据类型的具体分类与特征

JavaScript 语言标准目前定义了 8 种数据类型,其存储归属如下:

分类 包含类型 存储位置 数据大小 不可变性
基本数据类型 Number, String, Boolean, Undefined, Null, Symbol, BigInt 栈内存(Stack) 固定大小 值本身不可变(只读替换)
引用数据类型 Object, Array, Function, Date, RegExp, Map, Set 等 堆内存(Heap),栈中存指针 大小可动态增减 内容可直接在原内存上修改

注意细节:基本类型的值具有“不可变性(Immutability)”。例如对一个字符串变量进行拼接或替换字符,并不是在原有内存位置修改字符,而是开辟新空间生成新字符串并将变量指向新值;而引用类型的对象属性增删、数组元素的修改,都是直接在堆内存的原实体上操作。

三、变量声明与内存分配推导步骤

当一段 JavaScript 脚本执行时,引擎是如何一步步在底层分配这些内存的?下面通过一个包含基本类型和引用类型的具体代码段进行推导。

// 执行代码示例
let count = 10;
let user = { id: 101, role: "admin" };
let list = [1, 2];
JavaScript基本数据类型与引用类型在内存存储上有什么区别
图 2:不同变量类型在声明时的执行顺序与内存底层分配操作步骤

四、赋值与修改:值的复制 vs 引用的复制

内存存储位置的差异,直接决定了当把一个变量赋值给另一个变量时的行为模式。

1. 基本类型的按值复制

当把基本类型变量 a 赋值给 b 时,栈内存会为 b 开辟一块全新的独立空间,并把 a 的值完整复制过去。此后无论修改哪一个,彼此之间完全独立互不影响。

let a = 100;
let b = a; // 栈中新开辟空间,b = 100
b = 200;

console.log(a); // 100,原有变量不受任何影响
console.log(b); // 200

2. 引用类型的按引用复制

当把一个对象变量 obj1 赋值给 obj2 时,由于对象保存在堆中,栈中保存的仅仅是内存地址指针。因此,这次赋值只是在栈中复制了一份地址指针,两个变量最终指向堆内存中完全相同的同一块实体。

let obj1 = { score: 90 };
let obj2 = obj1; // 仅复制了指针,指向同一个堆对象

obj2.score = 100;

console.log(obj1.score); // 100!obj1 被间接修改
console.log(obj2.score); // 100
JavaScript基本数据类型与引用类型在内存存储上有什么区别
图 3:基本类型的独立值拷贝对比引用类型的地址共享模式

五、函数参数传递的本质验证

很多开发者常讨论“JavaScript 是按值传递还是按引用传递?”从底层内存机制来看,答案非常明确:JavaScript 中所有函数的参数传递都是按值传递(Pass by value)。

  • 对于基本类型,传递的是实际数值的副本。在函数内部对形参重新赋值,绝不会影响外部原变量。
  • 对于引用类型,传递的是保存在栈中的地址指针的副本。虽然复制的是指针,但由于副本和原指针存储的是同一个堆地址,修改形参属性会影响外部对象;但若在函数内给形参重新赋值一个新对象(即覆盖指针本身),外部原对象依然不受影响。
function modify(val, obj) {
  val = 999;               // 修改形参的基本值副本
  obj.name = "Updated";    // 顺着指针修改堆中的对象属性
  obj = { name: "Replaced" }; // 形参指针指向了新对象,切断了与原有对象的关联
}

let num = 10;
let person = { name: "Initial" };

modify(num, person);

console.log(num);         // 10(完全不受影响)
console.log(person.name); // "Updated"(属性被修改)

六、日常开发中的注意事项与最佳实践

理解栈与堆的存储差异,能够直接指导我们在编写代码时避开常见隐患:

  1. 避免意外的对象副作用:如果需要一个对象的独立副本,切勿直接使用 let copy = original;。应当根据结构层级选择浅拷贝(如 { ...original }、Object.assign())或深拷贝(如 structuredClone(original))。
  2. 理解判等符(== 与 ===):
    • 两个基本类型比较时,比对的是栈内存中的值本身(如 10 === 10 为 true)。
    • 两个引用类型比较时,比对的是栈内存中的地址指针。哪怕两个对象内容完全一样,只要它们在堆中不是同一个对象,比较结果也是 false(如 {} === {} 为 false)。
  3. 及时解除引用以协助垃圾回收:保存在堆中的对象如果没有任何栈变量持有其引用指针(引用计数为 0 或从根不可达),垃圾回收器(GC)就会将其回收。对于使用完毕的全局大对象,适时设置 obj = null 能够切断引用链,释放堆内存。
JavaScript基本数据类型与引用类型在内存存储上有什么区别
图 4:JavaScript 核心数据类型的内存机制与行为特征总结表
作者最新文章
相关文章
更多