为什么我们可以在 JavaScript 中创建稀疏数组?

dou*_*Ort 6 javascript sparse-matrix

var foo = new Array(20)我想知道、var foo = [1,2,3]; foo.length = 10或 等代码的用例是什么var foo = [,,,](另外,为什么要使用delete运算符而不是仅从数组中删除项目)。您可能已经知道,所有这些都会导致稀疏数组。

但为什么我们被允许做上述事情呢?为什么有人想要创建一个默认为 的数组length20如第一个示例中所示)?为什么有人想要修改和破坏length数组的属性(如第二个示例)?为什么有人想做类似的事情[, , ,]?为什么要使用delete而不是仅仅从数组中删除元素?有人可以为这些陈述提供一些用例吗?



我花了大约 3 个小时寻找一些答案。没有什么。大多数来源(2ality 博客、JavaScript:权威指南第 6 版,以及当您搜索“JavaScript 稀疏数组”之类的内容时在 Google 搜索结果中弹出的一大堆其他文章)说的唯一一件事是,稀疏数组是奇怪的行为,你应该远离他们。我读到的任何资料都没有解释或至少试图解释为什么我们首先被允许创建稀疏数组。除了 You Don't Know JS: Types & Grammar 之外,这本书讲述了为什么 JavaScript 允许创建稀疏数组:

一个数组在它的槽中没有明确的值,但是有一个 length 属性来暗示槽的存在,这是 JS 中一种奇怪的奇异数据结构类型,具有一些非常奇怪和令人困惑的行为。创建这样一个值的能力纯粹来自旧的、已弃用的历史功能(“类似数组的对象”,如参数对象)。

因此,这本书暗示该arguments对象以某种方式在某处使用我上面列出的示例之一来创建稀疏数组。那么,在哪里以及如何arguments使用稀疏数组呢?



另一件让我困惑的事情是《JavaScript:权威指南第六版》一书中的这一部分:

足够稀疏的数组通常以比密集数组更慢、更节省内存的方式实现。

对我来说,“内存效率更高”似乎与“速度较慢”相矛盾,那么两者之间有什么区别,尤其是在稀疏数组的情况下?是本书特定部分的链接。

Mas*_*nes 0

我想知道像 var foo = new Array(20), var foo = [1,2,3]; 这样的代码的用例是什么 foo.length = 10 或 var foo = [,,,] 是

理论上,出于同样的原因,人们通常使用稀疏数据结构(不一定按重要性顺序):内存使用量(var x = []; x[0]=123;x[100000]=456;不会消耗 100000 个“槽”)、性能(例如,取上述 x 的平均值,通过 for- in 或 reduce() )和便利性(没有“硬”越界错误,不需要显式增长/收缩);

也就是说,从语义上讲,js 数组只是一个特殊的关联集合,具有索引键和特殊属性“length”,满足大于其所有索引属性的不变量。虽然这是一个非常优雅的定义,但正如您所注意到的,它的缺点是渲染稀疏定义的数组有些混乱且容易出错。

但为什么我们被允许做上述事情呢?

即使我们不允许定义稀疏数组,我们仍然可以将未定义的元素放入数组中,从而导致与稀疏数组基本相同的可用性问题。所以,说,拥有[0,undefined,...,undefined,1,undefined]与 相同的东西[0,...,1,]只会给你带来更多的内存消耗数组和更慢的迭代。

足够稀疏的数组通常比密集数组以更慢、更节省内存的方式实现。对我来说,内存效率更高和速度更慢似乎是矛盾的

用于通用数据的“密集数组”通常被实现为充满相同大小元素的连续内存块;如果添加更多元素,则继续填充内存块,如果耗尽则分配新块。鉴于重新分配意味着将所有元素移动到新的内存块,通常会大量分配所述内存,以最大限度地减少重新分配的机会(类似于黄金比例乘以最后的容量)。因此,这样的数据结构通常对于有序/本地遍历来说是最快的(对 CPU/缓存更友好),对于不可预测的插入/删除来说是最慢的(对于足够大的 N ),并且具有很高的内存开销 ~ sizeof(elem) * N + extra未来元素的空间。

相反,“稀疏数组/矩阵/...”是通过将分布在内存中的较小内存块“链接”在一起或使用密集数据结构的某种“逻辑压缩”形式或两者来实现的;在任何一种情况下,由于明显的原因,内存消耗都会减少,但遍历它们相对需要更多的工作和更少的本地内存访问模式。

因此,如果与相同的有效遍历元素相比,稀疏数组消耗的内存要少得多,但比密集数组慢得多。然而,考虑到您使用带有稀疏数据的稀疏数组和对“零”起作用的算法,稀疏数组在某些情况下可以变得更快(例如,将非常大的矩阵与很少的非零元素相乘......)。