JS基本数据类型与引用类型的区别:内存存储与赋值行为解析

作者:闭包旅人时间:2026-10-30 17:59:09浏览:394

深入解析JavaScript中基本数据类型与引用类型的核心区别,从栈内存与堆内存存储机制切入,详解变量赋值、传参表现及修改联动效应。

在编写JavaScript代码时,我们经常会遇到变量重新赋值或修改属性后,其他变量是否同步受到影响的问题。导致这种现象的根本原因,在于JavaScript中基本数据类型与引用类型在底层内存架构中的存放方式以及赋值机制存在本质差异。理解栈内存(Stack)与堆内存(Heap)的分工,能帮助开发者准确预判变量的行为边界,杜绝数据意外篡改的隐患。

一、两大类型的分类与物理存放界限

JavaScript标准中目前定义了8种数据类型。其中7种属于基本数据类型(Primitive Types):Undefined、Null、Boolean、Number、String、Symbol 和 BigInt;而引用类型(Reference Types)统称为 Object,涵盖普通对象、数组、函数、日期(Date)以及正则(RegExp)等复合结构。

两者的首要差异体现在物理存储空间。基本数据类型占用的空间大小固定,访问频繁,JavaScript引擎将其直接存放在执行上下文的栈内存中;而引用类型的数据体积大小通常不可预知,并可在运行时动态扩展,因此实体数据存放于堆内存中,栈内存中仅保存一个指向该堆内存起始位置的引用地址(指针)。

JS基本数据类型与引用类型的区别:内存存储与赋值行为解析
图 1:基本类型与引用类型在栈内存与堆内存中的物理分布示意图

二、变量赋值操作的底层机制对比

在JavaScript中,无论是基本类型还是引用类型,使用赋值运算符 = 时的动作本质完全相同:在栈内存中开辟新槽位,并将右侧变量在栈中的二进制内容完整复制一份存入新槽位。差异在于,这个复制出的“栈内容”代表了截然不同的含义。

对于基本数据类型,栈内存储的就是数值本身,因此复制动作创建了一个完全独立的全新副本,后续对新变量的任何改动都不会回溯影响原变量:

let x = 100;
let y = x; // 栈内复制数值 100
y = 200;

console.log(x); // 输出: 100,原有变量未被改变
console.log(y); // 输出: 200

对于引用类型,栈内存储的内容是一个堆地址指针。赋值操作仅仅是将这个指针原样复制给了新变量,这意味着两个变量在栈中持有相同的内存地址,共同指向堆内存中的同一个实体对象。通过任何一个变量修改内部属性,另一个变量读取时都会观察到变更:

let objA = { count: 1 };
let objB = objA; // 复制了堆地址 0x...

objB.count = 2; // 通过指针修改同一个堆实体
console.log(objA.count); // 输出: 2,彼此产生联动影响
JS基本数据类型与引用类型的区别:内存存储与赋值行为解析
图 2:赋值操作在基本数据类型与引用类型中的内存演变逻辑对比

三、函数传参中的“按值传递”真相与误区

社区中常常有关于“JavaScript对象传参是按引用传递”的争论,这其实是一种概念混淆。在规范层面,ECMAScript中所有函数的形参传递都是严格的“按值传递”(Pass-by-Value)。

当把一个对象传递进函数时,传入的“值”并不是对象本体,而是该对象在栈中的地址副本。为了验证这一点,我们可以观察“修改属性”与“重新赋值整个形参”之间的显著行为差异:

function modify(item) {
  item.name = "Updated"; // 沿着地址修改了堆中实体
  item = { name: "Brand New" }; // 形参被赋予了全新堆地址,切断了原联系
}

let target = { name: "Original" };
modify(target);
console.log(target.name); // 输出: "Updated",而不是 "Brand New"

在上面的代码中,item.name = "Updated" 成功作用于外部对象,是因为此时 item 存有指向外部对象的相同地址。但当执行 item = { ... } 时,系统在堆中开辟了一个新的对象空间,并将新地址覆盖进局部变量 item 的栈槽位中,外部 target 的指针并未受到任何扰动。如果是真按引用传递,此时外部 target 应该变成 "Brand New"。这一推导步骤清晰证明了形参只是按值复制的指针。

JS基本数据类型与引用类型的区别:内存存储与赋值行为解析
图 3:函数调用传参时形参指针重赋值与属性修改的逻辑推导流程

四、比较运算符与数据深浅拷贝防范

存储机制的差异同样直接反映在全等运算符(===)的裁决标准上:

对基本类型使用 === 时,JavaScript只核对两者的值内容与类型是否一致;而对引用类型使用 === 时,比对的则是栈中的引用地址是否相同。即便两个对象具有完全一模一样的属性和键值,只要它们被分配在不同的堆内存块中,比较结果依然为 false:

let a = { id: 1 };
let b = { id: 1 };
console.log(a === b); // false,属于两个不同的堆空间

let c = a;
console.log(a === c); // true,持有完全相同的栈内指针
注意事项:很多开发者在需要解耦数据关联时,会采用展开运算符 const copy = { ...source } 或 Object.assign()。需要注意这只属于浅拷贝(Shallow Copy),它仅对对象第一层属性在栈中进行了复制。若对象内部嵌套了子对象或数组,嵌套属性依然只复制了引用地址,深层改动依旧会产生跨变量污染。针对深层复合结构,应采用 structuredClone() 或专业工具函数实现彻底的深度解耦。

五、核心特征总结与选型对照

为了在实际开发中更直观地对照与应用,下表与结构图对基本数据类型和引用类型的关键维度进行了全面归纳:

JS基本数据类型与引用类型的区别:内存存储与赋值行为解析
图 4:基本数据类型与引用数据类型在存储、赋值及比较行为上的总结对照

综上所述,JavaScript变量的核心行为由其内存布局决定。在处理基本类型时,我们可以放心地执行赋值与传参;而在操作引用类型时,需始终保持“共享引用”的思维警觉,在必要时通过适当的拷贝手段切断隐式依赖,从而保障程序状态的确定性与代码健壮性。

作者最新文章
相关文章
更多