当前位置: 首页 > 科技观察

JS中的变量是存放在堆上还是栈上?(深入内存原理)

时间:2023-03-12 21:12:21 科技观察

JavaScript基本类型是存放在堆中还是栈中?----基本类型之非基本类型看到这道题,相信大家都觉得这道题真的很基础,不能再基础了。随便百度一下,可以看到很多人说:基本类型存在于栈中,引用类型存在于堆中。真的那么简单吗?1.冰箱里放不下的大象让我们看一下这段代码:在这里,我们声明了一个大小为67MiB的字符串。如果字符串真的存在于栈中,那就不好解释了。毕竟,v8的默认堆栈大小是984KiB。一定不能保存。注意:在不同的时期,V8在不同的操作系统中对字符串的大小有不同的限制。大概有256MiB~1GiBnode--v8-options|grep-B0-A1stack-size的范围说到这里,你是不是已经在心里疑惑了?难不成是百度的回答错了,还得用谷歌搜索?让我们看看这里发生了什么。2.字符串constBasicVarGen=function(){this.s1='IAmString'this.s2='IAmString'}leta=newBasicVarGen()letb=newBasicVarGen()这里,我们声明了两个相同的对象,每个对象由两个相同的字符串组成.通过开发者工具,我们看到虽然我们声明了四个字符串,但是它们的内存指向同一个地址。备注:chrome无法查看实际地址,这里是抽象地址。这是什么意思?它表明引用地址存储在四个字符串中。所以上面放不下冰箱的大象就很好解释了。字符串并没有存入栈,而是存到另外一个地方,然后把这个地方的地址存入栈。好吧,我们来修改其中一个字符串的内容.s2='IAmString'调试前的内存快照调试后的内存快照,我们可以看到a.s0的初始内容是'IAmString',我们修改它的内容后,地址发生了变化。而我们新加的a.s2的内容是'IAmString',它的地址和其他值为'IAmString'的变量是一致的。当我们声明一个字符串时:1.V8内部有一个名为stringTable的hashmap来缓存所有的字符串。V8在读取我们的代码,对抽象语法树进行转换的时候,每遇到一个字符串,就会根据它的特性,将其转换为hash值,插入到hashmap中。以后如果遇到哈希值相同的字符串,会先取出来进行比较。如果一致,则不会生成新的字符串类。2.缓存字符串时,根据字符串的不同使用不同的哈希方法。那么我们来梳理一下,当我们创建一个字符串时,V8会先从内存(哈希表)中检查是否有一个完全一致的字符串已经被创建,如果存在则直接复用。如果不存在,则开辟一个新的内存空间来存放该字符串,然后将地址赋给该变量。这就是为什么我们不能通过下标直接修改字符串的原因:V8中的字符串是不可变的。以抄一份js的基本类型为例,说说v8的实现逻辑和大家理解的逻辑(亚文)//例子:vara="刘小撒";//V8读取字符串后,转到stringTable查找是否存在hashTable没有,插入'刘小撒'并将'刘小撒'的引用存入avarb=a;//直接复制'刘小撒'的引用b="谭雅文";//查找并存储在stringTable中='IAmString'aa.s3=a.s2+a.s0;//问题点:字符串拼接都进行了哪些操作?aa.s4=a.s2+a.s同时申请两个拼接的字符串,内容相同。可见其内容虽然相同。但是地址不一样。而且,地址前面的Map描述也发生了变化。如果字符串以传统方式存储(如SeqString),拼接操作的时间复杂度为O(n)。使用绳索结构[RopeStructure](即ConsString使用的数据结构)可以减少拼接的时间。如果字符串是这样,那么其他原始类型呢?3.像我亲眼所见的'oddball'说完字符串,再来看V8中另一个典型的'基本类型':oddBall。从oddBall扩展类型我们再做一个小实验:我们可以看到上图中列出的基本类型都有相同的地址。赋值时,也是原地复用。(而且这些从oddBall扩展出来的基本类型都有固定的地址,也就是说,V8第一次运行的时候,不管我们有没有声明过这些基本类型,它们都已经创建好了。而我们在声明对象的时候就给它们赋值了,它们的引用是赋值的,这也可以解释为什么我们说基本类型赋值给栈:在V8中,@73中存储的值永远是一个空字符串,那么v8可以等价地把这些地址赋值当做值本身.)我们看下源码验证一下:生成各种oddBall类型的方法,可以看出返回的是一个未定义赋值给变量的地址,实际上赋值的是偏移量定义的地址getRoot方法的第四,混淆数之所以称为混淆数,是因为在分配和改变的时候还没有搞清楚内存分配的机制。(它的内存是动态的)smi直接存放在内存中,范围是-231到231-1(231≈2*10?)integerheapNumbersimilarstringimmutablerange:用所有非smi数字的最低位来表示是否因为指针的最低位为1,所以是一个指针consto={x:42,//Smiy:4.2,//HeapNumber};o.x中的42会直接存储在对象本身作为Smi,而o.y中的4.2则需要另外开辟一个内存实体进行存储,o.y的对象指针指向内存实体。如果是32位操作系统,用32位来表示smi无可厚非,但是在64位操作系统中,为什么smi的取值范围也是-231到231-1(231≈2*10?)?ECMAScript标准规定数字需要被当作64位的双精度浮点数,但实际上,使用64位来存储任意数字其实是非常低效的(空间效率低,计算时间效率低,smi使用了大数位运算),因此,JavaScript引擎并不总是使用64位来存储数字。引擎可以在内部使用其他内存表示形式(例如32位),只要在数字之外可以监控的所有特性都与64位性能保持一致。constcycleLimit=50000console.time('heapNumber')constfoo={x:1.1};for(leti=0;i