本文深入解析JavaScript中的堆栈内存分配机制,区分基本类型与引用类型的存储差异。重点阐述传值与传址的区别,并通过代码示例详细说明浅拷贝与深拷贝的实现原理及内存表现,帮助开发者理解对象关联与独立性问题。
一.堆栈的定义
栈(Stack)本质上是一种特殊的线性数据结构。其核心特性在于对数据元素的插入和删除操作施加了严格限制:所有操作仅允许在线性表的一端进行。

基于上述操作限制,栈遵循“后进先出”(Last In First Out,简称LIFO)的原则。
栈结构在实际编程中有多种应用场景,例如数制转换、语法与词法分析以及数学表达式的求值处理等。
队列(Queue)同样属于运算受限的线性表,但其限制条件与栈不同。队列在两端均设有操作限制:数据插入只能在表的一端进行(只进不出),而数据删除只能在另一端进行(只出不进)。允许删除的一端被称为队尾(rear),允许插入的一端被称为队头(Front)。队列遵循“先进先出”(First In First Out,简称FIFO)的操作原则。

由于栈和队列均属于线性表范畴,它们可以采用顺序存储或链式存储两种结构。不同的存储结构会导致栈的基本运算算法实现存在差异。
二.JS堆栈研究
1、栈(stack)和堆(heap)
在JavaScript内存管理中,stack(栈)用于存储自动分配的内存空间,由系统自动管理释放;而heap(堆)用于存储动态分配的内存,其大小不固定且不会自动释放。
2、基本类型和引用类型
(1)基本类型:这类数据直接存放在栈内存中,属于简单数据段。由于数据大小确定,系统可以预先分配固定的内存空间。JavaScript中的5种基本数据类型包括Undefined、Null、Boolean、Number和String。它们按值存放,因此可以直接访问。
(2)引用类型:这类数据存放在堆内存中,表现为对象形式。变量实际保存的是一个指向堆内存的指针(地址),而非数据本身。由于对象空间大小各异,需要根据具体情况动态分配内存。
当访问引用类型(如对象、数组、函数等)的值时,JavaScript引擎首先从栈中获取该对象的地址指针,随后通过该指针从堆内存中读取实际数据。
3、传值与传址
明确内存中堆、栈的概念及变量类型,有助于深入理解“浅拷贝”与“深拷贝”的本质。基本类型与引用类型的最大区别在于传值与传址的行为差异。以下测试用例展示了这一现象:
var a = [1,2,3,4,5];
var b = a;
var c = a[0];
alert(b);//1,2,3,4,5
alert(c);//1
//改变数值
b[4] = 6;
c = 7;
alert(a[4]);//6
alert(a[0]);//1
通过上述测试可知,当修改变量b中的数据时,变量a中的数据也随之变化;然而,当修改变量c的数据值时,变量a并未发生改变。这体现了传值与传址的区别:a是数组,属于引用类型,将其赋值给b时,传递的是栈中存储的地址(相当于创建了一个指向同一堆内存的新指针),而非堆内存中的对象本身;而c仅从a的堆内存中获取了一个具体的数据值,并保存在栈中。因此,修改b时,程序会沿地址回到a所在的堆内存进行修改;而修改c时,仅在栈中直接更改值,无法影响a的堆内存。

三.拷贝
1.浅拷贝
前文提到,定义对象或数组时,变量通常只存储一个地址。执行对象拷贝时,若属性值为对象或数组,传递的依然是地址。因此,子对象在访问该属性时,会根据地址回溯到父对象指向的堆内存中,导致父子对象产生关联,两者的属性值指向同一内存空间。
var a = {
key1:"11111"
}
function Copy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
return c;
}
a.key2 = ['小辉','小辉'];
var b = Copy(a);
b.key3 = '33333';
alert(b.key1); //1111111
alert(b.key3); //33333
alert(a.key3); //undefined
a对象中key1属性是字符串,key2属性是数组。a拷贝到b,12属性均顺利拷贝。给b对象新增一个字符串类型的属性key3时,b能正常修改,而a中无定义。说明子对象的key3(基本类型)并没有关联到父对象中,所以undefined。
b.key2.push("大辉");
alert(b.key2); //小辉,小辉,大辉
alert(a.key2); //小辉,小辉,大辉
然而,若修改的属性变为对象或数组时,父子对象之间就会发生关联。从上述弹出结果可知,对b对象进行修改后,a和b的key2属性值(数组)均发生了改变。其内存状态可通过图示理解。

原因在于key1的值属于基本类型,拷贝时传递的是数据段本身;而key2的值是堆内存中的对象,拷贝时传递的是指向key2对象的地址。无论复制多少个key2,其值始终指向父对象key2所在的内存空间。
2.深度拷贝
上述浅拷贝结果可能并非实际编码所需,因为通常不希望父子对象之间产生关联。此时可使用深拷贝。既然属性值为数组或对象时只会传址,可以通过递归解决此问题,遍历父对象中所有属于对象的属性类型并赋给子对象。测试代码如下:
function Copy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
Copy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
a.key2 = ['小辉','小辉'];
var b={};
b = Copy(a,b);
b.key2.push("大辉");
alert(b.key2); //小辉,小辉,大辉
alert(a.key2); //小辉,小辉
由上述结果可知,修改b的key2数组时,并未使a父对象中的key2数组新增值,即子对象未影响父对象a中的key2。其存储模式大致如下:

以上就是详解JavaScript堆栈与拷贝的详细内容,更多关于JS 堆栈 拷贝的资料请关注本站其它相关文章!