【问题标题】:Memory management of JavaScript Array [duplicate]JavaScript Array的内存管理[重复]
【发布时间】:2012-09-20 08:25:09
【问题描述】:

可能重复:
Are Javascript arrays sparse?

我目前正在学习 JavaScript,并且一直在阅读一些简单的介绍和教程。在查看 Array 对象时,我偶然发现了一些细节,这让我觉得很奇怪,来自其他语言,如 C/Java/Scala/...


假设我们这样定义一个数组:

var arr = ['foo','bar','qux']

我们现在分配

arr[5] = 'baz'

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

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

而且长度符合预期

arr.length
>> 6

JavaScript 已经将我们的数组扩展为所需的长度 - 六 - 并且新项目设置为未定义 - 除了我们实际分配值的那个。

从低层次的角度来看,这在记忆方面是可怕的。通常,数组将是内存中的连续范围 - 使数组更大通常涉及将整个数组复制到足够大小的新内存位置。这是一项非常昂贵的操作。

现在,我确实意识到这可能 不是 JavaScript 引擎正在做的事情,因为围绕数组进行复制会非常昂贵,并且内存空间会浪费在所有这些“未定义”上值。

谁能告诉我门后到底发生了什么?

  • 数组实际上是某种排序链表吗?
  • “未定义”数组项是否真的存在?
  • 处理大部分填充了“未定义”的大型数组的成本有多高?

【问题讨论】:

  • JS 是否只有一个自动垃圾收集器来清理混乱?
  • 这确实看起来是重复的。考虑到第一个问题的标题和标签,虽然我没有找到它,但我并不觉得太糟糕...... ;)

标签: javascript arrays memory memory-management


【解决方案1】:

在 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 扫描对象和原型并且找不到您要查找的内容时的默认返回值:“对不起,什么你问我在我的书中是未定义的。” 就是它所说的。

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

更新:

只要quoting the Ecma std:

15.4 数组对象
数组对象对某一类属性名称给予特殊处理。属性名称 P(形式为 String value) 是一个数组索引当且仅当 ToString(ToUint32(P)) 等于 P 并且 ToUint32(P) 不等于 2^32 1。属性名称为数组索引的属性也称为元素。每个 Array 对象都有一个 length 属性,其值始终是小于 2^32 的非负整数。长度值 property 在数值上大于名称为数组索引的每个属性的名称;每当一个 创建或更改 Array 对象的属性,根据需要调整其他属性以保持此 不变的。具体来说,每当添加名称为数组索引的属性时,长度属性为 如有必要,更改为比该数组索引的数值大一;并且每当长度 属性被更改,每个属性名称是一个数组索引,其值不小于新的 长度被自动删除。此约束仅适用于 Array 对象的自身属性,并且是 不受可能从其原型继承的长度或数组索引属性的影响。

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

【讨论】:

  • 当然,也可以将属性显式设置为undefined,这种情况下属性实际上是存在的。
  • 是的,当然,但在那种情况下.hasOwnProperty 返回 true,添加了一个代码示例来反映这一点
  • 您也可以在常规对象上创建数字属性(尽管属性名称总是转换为字符串)。与数组的唯一区别是设置数值属性也会更新length 属性。
  • 我指的那句话是“数组是对象,允许你用数字实例引用它们的属性”,在我看来这意味着非数组对象不允许这样做,但我认为这只是一个小小的歧义。抱歉学究了。
  • @shinzou: JS 本来没有数组,所以它们从来没有简单获取的性能优势。他们只是扩充了哈希表实现,改为使用 int 值进行哈希。我猜 JS 只是一种在浏览器中运行的高级语言。这并不是说在这一切之下,某些引擎可能不会优化数组类型以实际映射到非散列映射上。 IIRC V8 引擎只是创建了一个底层 C++ 样式对象,有效地呈现所有查找 O(1) 操作
【解决方案2】:

有人告诉我,数组分为两部分,[值,指针]。所以 arr[2] 的指针为空。当您添加 5 时,它会将地址从 null 更改为指向数字 3,该数字指向数字 4,该数字指向数字 5,该数字为 null(因此数组结束)。

我不确定这是多么真实,因为我从未真正检查过它。但这似乎是有道理的。

所以你不能像在 ac 类型数组上那样做数学运算(即要达到值 4,只需执行起始内存点 + 4x(内存中的对象数量)),但你可以通过 peice 跟随数组 peice 来做到这一点

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 未定义的属性不会仅仅因为它们不存在而占用任何内存。
    【解决方案4】:

    答案:

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

    【讨论】:

      【解决方案5】:

      数组只是对象的有序列表。 在 JavaScript 中,一切都是对象,因此数组并不是我们所知道的真正的数组 :)

      你可以找到小内部here

      对于您对使用大型数组的疑问......好吧,请记住,您在“客户端”进行的计算越少,您的页面就会越快。

      【讨论】:

      • 并非所有事物都是对象。字符串、数字、布尔值、未定义和 null 不是对象。它们是文字值(除非您专门创建可以作为对象的对象)。
      • 字符串不是对象吗?那为什么他们有方法呢?
      • @Philipp:他们没有。它们之所以出现,是因为在调用方法时它们会自动转换为 String 对象。 stackoverflow.com/a/9110389/96100
      猜你喜欢
      • 1970-01-01
      • 2014-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-27
      • 2012-08-08
      • 2010-12-03
      • 1970-01-01
      相关资源
      最近更新 更多