JavaScript基本数据类型与引用类型的区别:内存存储与赋值机制

Mason
Mason
发布时间:2026-09-23| 3人浏览过
原创

在 JavaScript 开发中,经常遇到“修改了一个变量,另一个变量也跟着变了”的情况。这并非 Bug,而是由数据类型的存储机制决定的。理解基本类型(值类型)与引用类型在内存中的分布,是掌握变量赋值、函数传参以及对象拷贝的基础。

基本类型包括 Number、String、Boolean、Null、Undefined、Symbol 和 BigInt,它们直接存储在栈内存中;而引用类型如 Object、Array、Function,其实际数据存储在堆内存中,栈中仅保存指向堆的指针。这种差异直接导致了两者在赋值和比较时的行为截然不同。

JavaScript基本数据类型与引用类型的区别:内存存储与赋值机制

JavaScript 内存模型:栈存储基本类型,堆存储引用类型数据

1. 观察内存存储结构的差异

要理解行为差异,首先要看清数据住在哪里。我们可以将内存简化为两个区域:栈(Stack)和堆(Heap)。

基本类型的数据体积小、大小固定,直接存放在栈中。当你声明一个基本类型变量时,栈中会开辟一块空间,直接存入该值。

let a = 10;
let b = 'Hello';

此时,栈内存中 a 对应的格子存的是 10,b 对应的格子存的是 'Hello'。访问这些变量时,系统直接从栈中读取值,速度极快。

引用类型的数据体积大、大小不固定,存放在堆中。栈中只保留一个“地址”或“指针”,指向堆中的具体位置。

let obj = { name: 'Alice' };

此时,栈中的 obj 存的是一个类似 0x00123 的地址,而真正的 { name: 'Alice' } 对象数据位于堆内存中。访问 obj.name 时,系统先查栈拿到地址,再根据地址去堆里找数据。

JavaScript基本数据类型与引用类型的区别:内存存储与赋值机制

赋值机制对比:基本类型拷贝值,引用类型拷贝地址

2. 验证赋值操作的独立性与共享性

赋值操作最能体现两者的区别。我们分别对基本类型和引用类型进行赋值,观察结果。

对于基本类型,赋值是值的拷贝。新变量获得了一份独立的数据副本,互不影响。

let x = 100;
let y = x; // 将 x 的值 100 拷贝给 y

y = 200;   // 修改 y

console.log(x); // 输出 100,x 不受影响
console.log(y); // 输出 200

在这个过程中,y 拥有自己独立的栈空间,修改 y 不会触动 x。

对于引用类型,赋值是引用的拷贝(即指针拷贝)。新变量获得的是同一个堆内存地址,两者指向同一份数据。

let userA = { age: 25 };
let userB = userA; // 将 userA 的地址拷贝给 userB

userB.age = 30;    // 通过 userB 修改堆中的数据

console.log(userA.age); // 输出 30,userA 也变了
console.log(userB.age); // 输出 30

因为 userA 和 userB 指向堆中同一个对象,任何一方修改数据,另一方都能感知到。这就是所谓的“共享引用”。

JavaScript基本数据类型与引用类型的区别:内存存储与赋值机制

引用类型比较:即使内容相同,地址不同则不相等

3. 测试比较逻辑的本质

由于存储方式不同,比较运算符 == 或 === 的行为也不同。

基本类型比较的是值。只要值相同,就相等。

console.log(10 === 10);       // true
console.log('abc' === 'abc'); // true

引用类型比较的是地址(指针)。即使两个对象的内容完全一样,只要它们在堆中是不同的实体,地址就不同,比较结果即为 false。

let obj1 = { val: 1 };
let obj2 = { val: 1 };

console.log(obj1 === obj2); // false,因为地址不同

let obj3 = obj1;
console.log(obj1 === obj3); // true,因为指向同一个地址

这一特性在判断对象是否相同时非常关键,不能简单地用 === 来判断两个结构相同的对象是否“相等”,通常需要借助 lodash 的 isEqual 或手动遍历属性。

4. 实现浅拷贝与深拷贝的效果

在实际开发中,我们经常需要复制对象而不希望影响原数据。这就涉及到了浅拷贝和深拷贝。

浅拷贝只复制第一层属性。如果属性值是基本类型,则拷贝值;如果是引用类型,则拷贝地址。常用的 Object.assign() 和展开运算符 ... 都是浅拷贝。

let original = { 
  name: 'Bob', 
  skills: ['JS', 'CSS'] 
};

// 使用展开运算符进行浅拷贝
let copy = { ...original };

copy.name = 'Tom';       // 修改基本类型属性,不影响原对象
console.log(original.name); // 'Bob'

copy.skills.push('HTML'); // 修改引用类型属性,影响原对象
console.log(original.skills); // ['JS', 'CSS', 'HTML']

可以看到,修改 copy.skills 导致 original.skills 也发生了变化,因为数组是引用类型,浅拷贝只复制了数组的地址。

深拷贝则会递归复制所有层级的数据,生成一个完全独立的新对象。最简单的方式是使用 JSON.parse(JSON.stringify()),但它有局限性(如无法处理函数、undefined、循环引用等)。

let deepCopy = JSON.parse(JSON.stringify(original));

deepCopy.skills.push('Vue');
console.log(original.skills); // ['JS', 'CSS', 'HTML'],不受影响

更稳健的深拷贝可以使用结构化克隆算法 structuredClone()(现代浏览器支持)或第三方库如 Lodash 的 _.cloneDeep()。

JavaScript基本数据类型与引用类型的区别:内存存储与赋值机制

浅拷贝只复制第一层,深拷贝递归复制所有层级

5. 确认最终效果与注意事项

通过上述步骤,我们明确了:基本类型存在栈中,赋值即拷贝值,彼此独立;引用类型存在堆中,赋值即拷贝地址,共享数据。

在编写代码时,如果需要保留原始数据的状态,务必注意引用类型的赋值行为。对于简单的配置对象,浅拷贝通常足够;对于复杂的嵌套数据或状态管理,应优先考虑深拷贝或使用不可变数据结构(Immutable Data)策略。

理解这一机制,不仅能避免意外的数据修改 Bug,还能更好地优化内存使用,例如及时将不再使用的引用类型变量置为 null,以便垃圾回收机制释放堆内存。

JavaScript基本数据类型与引用类型的区别:内存存储与赋值机制

开发建议:根据场景选择拷贝策略,注意内存释放

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。