JavaScript数组的内存管理

15

可能是重复问题:
JavaScript数组是否稀疏?

我正在学习JavaScript,并阅读一些简单的介绍和教程。在查看Array对象时,我偶然发现了一些细节,这些细节对我来说非常奇怪,因为我来自其他语言(如C/Java/Scala/...)。


假设我们将一个数组定义如下:

var arr = ['foo','bar','qux']
我们现在将其分配。
arr[5] = 'baz'

这将导致我们的数组看起来像这样:

arr
>> ["foo", "bar", "qux", undefined, undefined, "baz"]

长度符合预期

arr.length
>> 6

JavaScript会将数组自动扩展到所需的长度 - 六个 - 新元素的值设置为undefined,我们实际赋值的那一个除外。

从低级别的角度来看,这在内存方面是非常糟糕的。通常情况下,一个数组在内存中是一段连续的区域 - 将数组变大通常需要将整个数组复制到一个足够大小的新内存位置。这是一项非常昂贵的操作。

我确实意识到JavaScript引擎实际上 不是 这样做的,因为复制数组会非常耗费性能,而且所有这些'undefined'值都会浪费内存空间。

有人能告诉我幕后发生了什么吗?

  • 数组实际上是某种链表吗?
  • 'undefined'数组项实际上存在吗?
  • 使用大量填充了'undefined'的大型数组的处理开销有多大?

JS不是有一个自动垃圾回收器来清理这个混乱吗? - Alex Gill
这似乎实际上是一个重复的问题。考虑到第一个问题的标题和标签,我对于没有找到它并不感到太难过... ;) - fgysin
5个回答

16
在 JavaScript 的第一个版本中,没有数组。它们后来作为“所有对象之母”Object的子类引入。你可以通过执行以下操作轻松测试这一点:
var foo = [1,2,3,4];
for (var n in foo)
{//check if n is equal (value and type) to itself, coerced to a number
    console.log(n === +(n) ? 'Number' : 'String');
}

这将会记录 String,一遍又一遍。内部所有数字键都会被转换为字符串。Length 属性仅获取最高索引,并加 1。没有更多的操作。当显示数组时,对象被迭代,对于每个键,与任何对象相同的规则适用: 首先扫描实例,然后是原型... 所以如果我们稍微改变一下代码:
var foo = [1,2,3,4];
foo[9] = 5;
for (var n in foo)
{
    if (foo.hasOwnProperty(n))
    {//check if current key is an array property
        console.log(n === +(n) ? 'Number' : 'String');
    }
}

你会注意到这个数组只有5个自身属性,键为undefined的4-8是未定义的,因为在实例中没有找到对应的值,也没有在任何底层原型中找到。简而言之:数组并不是真正的数组,而是表现类似的对象。

正如Tim所说,你可以有一个包含未定义属性的数组实例,该属性确实存在于该对象中:

var foo = [1,2,undefined,3];
console.log(foo[2] === undefined);//true
console.log(foo[99] === undefined);//true

不过,这里仍然有一个区别:

console.log((foo.hasOwnProperty('2') && foo[2] === undefined));//true
console.log((foo.hasOwnProperty('99') && foo[99] === undefined));//false

回顾,你的三个主要问题:

  • 数组是对象,允许您使用数字实例引用其属性

  • undefined值不存在,当JS扫描一个对象和其原型并找不到您正在寻找的内容时,它只是默认返回值:它说:“对不起,在我的书里,我没有找到您要求的东西。”

  • 使用大部分未定义的数组不会影响对象本身的大小,但访问未定义的键可能会非常,非常微小地变慢,因为还必须扫描原型。

更新:

引用Ecma std

15.4数组对象

数组对象会对某些类别的属性名给予特殊处理。一个属性名P(以字符串值的形式)仅在ToString(ToUint32(P))等于P且ToUint32(P)不等于2^32   1时是数组索引。名称为数组索引的属性也称为元素。每个数组对象都有一个length属性,其值始终是小于2 ^ 32的非负整数。length的值   属性在名称为数组索引的每个属性的名称大于其名称时数值上均大于;无论何时添加其名称为数组索引的属性,如有必要,则更改length   属性,使其比该数组索引的数字值大1; 每当更改length时   属性,自动删除其值不小于新长度的每个名称为数组索引的属性。此约束仅适用于Array对象的自有属性,并且不受其原型继承的length或数组索引属性的影响。

如果以下算法返回true,则称对象O稀疏:
1. 让len成为使用参数“ length”调用O的[[Get]]内部方法的结果。
2. 对于范围为0≤i的每个整数i
    a. 允许elem使用参数调用O的[[GetOwnProperty]]内部方法       ToString(i)。
     b. 如果未定义elem,请返回true。
3. 返回false。


当然,也可以显式地将属性设置为 undefined,这样该属性实际上就存在了。 - Tim Down
你也可以在普通对象上创建数字属性(尽管属性名总是转换为字符串)。与数组的唯一区别在于,设置数字属性还会更新“length”属性。 - Tim Down
当然,我没有任何地方与此相矛盾吧?基本上:数组原型增强了“Object”原型,因此甚至将数组键转换为数字。 我上次检查时,“length”只是设置为数组实例的最高键/名称的属性+1。 它在对数组进行的每个更改时都会更新,就像我在我的答案15.4 Array Objects-第一段中所述的那样。 老实说,我看不出我在哪里与你所说的任何事情相矛盾,或者我错过了什么吗? - Elias Van Ootegem
1
我所指的句子是“数组是对象,允许您使用数字实例引用它们的属性”,这似乎暗示非数组对象不允许这样做,但我认为这只是一个小模糊。抱歉我这么啰嗦。 - Tim Down
1
@shinzou:JS 最初没有数组,因此它们从一开始就没有简单获取的性能优势。他们只是增强了哈希表实现,使用 int 值进行哈希。我想 JS 只是一个太高级的语言,无法在浏览器中运行。这并不是说,在所有底层操作下,某些引擎可能不会将数组类型优化为实际映射到非哈希映射上。如果我没记错的话,V8 引擎只创建一个基于 C++ 风格的对象,有效地使所有查找操作 O(1)。 - Elias Van Ootegem
显示剩余4条评论

4

数组就是一系列有序的对象。在JavaScript中所有东西都是对象,所以我们所认识的数组实际上并不是真正的数组 :)

你可以在这里找到更多有关数组的内部细节。

关于处理大型数组的疑问......请记住,您在“客户端”执行的计算越少,页面加载速度就越快。


4
并非所有东西都是对象。字符串、数字、布尔值、未定义和空值并不是对象,它们是字面值(除非你明确地将其中可作为对象的部分创建为对象)。 - James Allardice
字符串不是对象吗?那为什么它们有方法呢? - Philipp
3
@Philipp说:它们并不是真正的字符串(String),只是在调用方法时自动转换为字符串对象。https://dev59.com/GGox5IYBdhLWcg3wl1T4#9110389 - Tim Down

1

答案:

  1. 在JavaScript中,数组与对象相同(即无序属性集合),具有一个神奇的length属性和额外的原型方法(push()等)。
  2. 不,未定义的项不存在。JavaScript有一个in运算符,用于测试属性的存在性,您可以使用它来证明这一点。因此,对于以下数组:var arr = ['foo']; arr[2] = 'bar';2 in arr返回true1 in arr返回false
  3. 稀疏数组所占用的内存应该不会比密集数组多,其长度是实际在稀疏数组中定义的属性数。只有在迭代其未定义属性时,才会更昂贵地处理稀疏数组。

0

大多数JavaScript实现将数组实现为二叉树或哈希表的某种形式,其中数组索引作为键,因此大范围的未定义对象不会占用任何内存。


未定义的属性不会占用任何内存,因为它们不存在。 - Tim Down

0

我被告知数组由两部分组成,即[value, pointer]。因此,arr[2]的指针为null。当您添加5时,它将地址从null更改为指向数字3,该数字指向数字4,该数字指向数字5,最后是null(因此到达数组末尾)。

我不确定这是否完全正确,因为我从未实际检查过。但是,这似乎是有道理的。

因此,您不能像在c类型数组上那样进行数学计算(例如,要获取值4,只需执行起始内存点+ 4x(内存中的对象数量)),但是您可以通过逐个跟随数组来完成它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接