学习堆内存,栈内存时遇到一些疑惑
下面是我所知道的,有不对的地方欢迎指出:
js中基础变量存在栈内存中,如var a = 3;其中3存在于常量池中,并对应一个编号xx,而栈内存中则存着a对应xx,这样形成a->xx->3,从栈内存指向常量池。
而对象则存在堆内存中,如var b = {};在堆内存中存在一个对象{},对应一个编号yy,而在栈内存中存在一个b对应yy,形成一个b->yy->{}的形式,从栈内存指向堆内存。
那我现在有个问题,如果var c = {d:1};栈内存中是只存了c对应{d:1}的地址编号,还是同时存了一个c.d对应常量池中1的对应编号?也就是说,当我访问c.d时,具体的访问,是怎么走的?
另一个问题,所谓将{}存在堆内存中,{}在堆内存中是个什么样子的?一串二进制代码吗?
这要从基础的讲起,为什么要区分堆和栈
首先,在支持递归调用的语言里,局部变量是有多个的,比如说
function sum(i)
{
Console.Log(i);
if (i == 0) return 0;
var j = sum(i - 1);
return i + j;
}
这里面的j,在递归调用的时候,有很多个,那么怎么办?显然堆栈是最好的选择,每次调用,都把它放进去,退出调用就自动清掉了。
那么什么时候用到堆?
如果一个变量的生命周期超过了函数的调用,那么用堆栈的话,函数退出了,这个变量还要保留,怎么办?存在哪里?就要一个全局的内存块把塔门接住。
然后为什么要常量池?
这个其实是为了性能的优化的考虑,对于不会变化的数据,比如字符串,那么可以让多个引用指向同一个变量,以达到节约内存的目的。
你要先搞清楚这些
1.第一个问题,栈内存确实是只存了c对应{d:1}的地址编号,具体流程基本上是这样的:
访问C变量—>拿到C变量存储的堆地址—>访问对应的堆地址—>拿到地址存储的数据—>从数据中拿到d和其对应的值。
2.第二个问题,对象在堆内存中确实是一段二进制数据,不过这段数据的格式是有明确规定的,可以简单概括为这样:
(一段二进制表示对象开始)数据 数据 数据 数据 数据(一段二进制表示对象结束)
实际的存储格式比这个要复杂,不过初学者的话了解到这里就够了,通过这样的存储方式只要栈中保存堆内对象的开头位置,然后从这个位置一直往后读,直到读取到结束位置就能读到整个对象,这也是为什么堆中可以存储很大的数据。
在JavaScript中,对于对象类型的变量,存储在堆内存中的是对象本身的数据结构,而在栈内存中存储的是对象在堆内存中的地址。
所以,当执行 var c = {d:1}; 时,栈内存中存储的是变量 c 对应的堆内存中的地址,堆内存中存储的是一个对象,该对象有一个属性 d,值为 1。而此时,常量池中并没有单独存储一个编号来表示 1,而是直接将 1 作为对象的属性值。
因此,在访问 c.d 时,具体的访问过程如下: 1. 根据栈内存中的 c,找到堆内存中的地址。 2. 根据这个地址,在堆内存中找到对应的对象。 3. 在对象中找到属性 d,获取其值。
所以,访问 c.d 实际上是通过指针的方式在堆内存中找到对象的属性值。
在堆内存中,{} 表示一个空对象,它是一个由属性和方法组成的集合。对象可以通过属性和方法来描述其状态和行为。
具体来说,对象是由一些键值对组成的,其中键是属性名,值可以是任意的 JavaScript 数据类型。例如,在堆内存中的一个空对象可以表示为:
{
// 属性为空
}
这个空对象中没有属性,但是你可以通过添加属性来描述对象的内容。例如:
{
name: "John",
age: 25,
isStudent: true
}
在这个示例中,对象有三个属性:name、age 和 isStudent,它们的值可以是字符串、数字或布尔值。
所以,{} 在堆内存中并不是一串二进制代码,而是一个包含属性和方法的集合,通过键值对的形式进行描述。