JS的数组数据结构的真相

16 阅读4分钟

前段时间,和小伙伴们有过一个争执。那就是js数组是怎么分配内存的?

我之前学过一点Java。所以我认为,数组是在堆内存中开辟了一个连续的内存空间,每个元素占用一个空间。好处是查找元素非常快,只要知道数组的第0个元素的位置,其他的元素只需要索引号乘以空间尺寸就可以了。但是,当我们要扩展数组长度时就比较慢了。如果是要扩充数组,是需要重新开辟空间,然后将原来位置的内容都复制过去,回收原本的空间。

但是,有小伙伴却认为不是这样的。他认为,js数组是Object的一种特殊形式,它的索引等同于Object的key。元素等同于Object的value。因此,数组元素在堆内存中是散列的。

最后,我们谁也不服谁,不了了之。

既然大家存在争议,我又是个喜欢钻研的人。因此,本着不抛弃,不放弃的精神。经过在网络上采百家之言,最后我总结了下JS数组的数据结构的真相。

对于JS数据结构的内存分配,主要还是要看JS引擎,不同的JS引擎也是存在着差异的,也就是实现是不一样的。我们就以chrome的V8引擎为例(毕竟它代表着Nodejs),V8引擎在实现数组的时候为了做性能优化是既要,也要。具体是什么情况呢?我给大家具体说说。

我们先看JS数组的特性:

  • 1、动态大小:JavaScript数组的大小是动态的,可以随着数据的添加和删除而自动调整。
  • 2、索引:数组中的每个元素都有一个索引,从0开始。例如,在数组[1, 2, 3]中,1的索引是0,2的索引是1,以此类推。
  • 3、异构元素:数组可以包含不同类型的元素,如数字、字符串、对象甚至其他数组。
  • 4、内部机制:在内部,JavaScript数组是基于对象的。数组对象具有一些特殊的内部属性,如length属性,它反映了数组中元素的数量。
  • 5、稀疏数组:JavaScript数组可以是稀疏的,即数组中可以有“空”元素。在稀疏数组中,某些索引没有对应的元素。
  • 6、数组原型:JavaScript中的数组都继承自Array.prototype,这意味着所有数组都可以使用定义在Array.prototype上的方法。

在V8中存在两种数组,一种叫快数组,一种叫字典数组也称为慢数组。

所谓的快数组就是我之前提到的在堆内存中开辟连续的内存空间,每一个小空间内都存放一个元素。因此它的增删改查都很快,扩展比较麻烦。

image.png

而慢数组实际上是采用类似哈希表的数据结构实现的,也就是通过哈希函数将每个元素的索引映射到内存中的位置。

image.png

不管是快数组还是慢数组,我们都是不能直接声明的,而是由V8引擎根据不同的情况来自动实现的。

那么V8会在什么情况使用快数组,在什么情况下使用慢数组呢?

根据差到的资料我总结了以下两点:

  • 1、当数组中是一些简单的,没有空值的元素时,js引擎会自动使用快数组进行存储
  • 2、当数组是不连续的(有“空(empty)”值),或者是很复杂的字符串时,js引擎就会自动转换成慢数组。

例如: const arr1 = [0,1,'你好',Object,null,undefined]; const arr2 = new Array(10)

在这个例子中,arr1虽然元素的数据结构不同但还是连续的存储,数组arr2被初始化为一个包含10个空位的数组。尽管数组中目前没有实际元素,但它的索引从0到9是连续的。在大多数JavaScript引擎中,包括Chrome的V8引擎,这种类型的数组会被优化为快数组。

如果添加赋值: arr2[12] = 1

添加arr2[12] = 1之后,数组的索引不再连续。在JavaScript中,数组的索引通常是连续的,从0开始递增。当数组中的索引变得不连续时,JavaScript引擎会自动将数组的实现方式从快数组转换为慢数组。

所谓的慢数组就是因为它的元素在内存中是不连续的存储,所以它查找元素时,需要先通过一个哈希函数通过索引计算出真实的存储位置才能找到对应的元素,而这个过程比快数组的查找要慢一些。