JavaScript Array的内存管理

fgy*_*ica 14 javascript memory arrays memory-management

可能重复:
Javascript数组是否稀疏?

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


所以我们假设我们定义一个数组:

var arr = ['foo','bar','qux']
Run Code Online (Sandbox Code Playgroud)

我们现在分配

arr[5] = 'baz'
Run Code Online (Sandbox Code Playgroud)

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

arr
>> ["foo", "bar", "qux", undefined, undefined, "baz"]
Run Code Online (Sandbox Code Playgroud)

长度如预期

arr.length
>> 6
Run Code Online (Sandbox Code Playgroud)

JavaScript已经将我们的数组扩展到所需的长度 - 六 - 并且新项目被设置为undefined - 除了我们实际赋值的那个.

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

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

有人能告诉我门后究竟发生了什么吗?

  • 数组实际上是某种链接列表吗?
  • "undefined"数组项目实际存在吗?
  • 使用大多数充满'undefined'的大型数组有多贵?

Eli*_*gem 15

在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');
}
Run Code Online (Sandbox Code Playgroud)

这将记录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');
    }
}
Run Code Online (Sandbox Code Playgroud)

您会注意到数组只有5个自己的属性,undefined键4-8未定义,因为实例中没有找到相应的值,也没有任何底层原型.简而言之:数组不是真正的数组,而是行为相似的对象.

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

var foo = [1,2,undefined,3];
console.log(foo[2] === undefined);//true
console.log(foo[99] === undefined);//true
Run Code Online (Sandbox Code Playgroud)

但同样,存在差异:

console.log((foo.hasOwnProperty('2') && foo[2] === undefined));//true
console.log((foo.hasOwnProperty('99') && foo[99] === undefined));//false
Run Code Online (Sandbox Code Playgroud)

RECAP,您的三个主要问题:

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

  • 这些undefined值不存在,它们只是JS扫描对象和原型时的默认返回值,并且无法找到您要查找的内容:"抱歉,您在我的书中提到的内容未定义." 就是这么说的.

  • 使用很大程度上未定义的数组不会影响对象本身的大小,但访问未定义的键可能会非常非常慢,因为原型也必须进行扫描.

更新:

引用Ecma标准:

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

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