JavaScript数组如何存储像“02”这样的键?

3
我正在阅读《JavaScript权威指南》,看到了一段话,引用如下:
如果使用一个非负整数的字符串作为数组索引,它会像数组索引一样而不是对象属性进行处理。
因此,类似a["4"]的内容将被存储在索引为5的位置,而类似a["-1.26"]的内容将被存储为属性"1.26"
我有点冒险,尝试了a["02"]=2;。现在我可以设置它,检索它,但它既没有被设置为a[2](基本上是a[parseInt("02")]),也无法在打印数组时获取它。
以下是我的代码。我尝试在node和浏览器中运行它。
> a[3] = 3;
3
> a["-1.2"] = 10;
10
> a
[ , , , 3, '-1.2': 10 ]
> a["02"] = 2;
2
> a
[ , , , 3, '-1.2': 10 ]
> a["02"]
2
> a.length
4

我只是想了解到底发生了什么。


当索引不是数字时,数组会回滚到使用对象属性。 - m_vdbeek
不建议混合使用数组式访问和对象式访问,因为行为会有所不同(例如排序顺序),但是本质上数字键会获得一些特殊处理。 - Dave
我认为你所看到的可能只是JavaScript控制台在转储数组内容时的一种现象。 "02"和"-1.2"作为属性名称之间实际上没有区别;两者都不会被计算为数组索引。 - Pointy
我会说,所有符合正则表达式/^0|[1-9][0-9]*$/的内容都被视为数字。a["+1"]并不等同于a[1] - xanatos
3个回答

3

来自规范

如果且仅当ToString(ToUint32(P))等于P并且ToUint32(P)不等于2^32-1时,属性名P(以字符串值形式)是一个数组索引。

因此,这本书是误导性的,因为它需要完全是整数,没有前导零等。

因此,"02"被视为"hellokitty" - 在任何意义上都不是索引属性。

考虑:

var P = "02"
console.log( ( P ) === ( ( +P >>> 0 ).toString() ) );
//false

var P = "2"
console.log( ( P ) === ( ( +P >>> 0 ).toString() ) );
//true

这里有一个函数。
function isStringConsideredArrayIndex( P ) {
    if( typeof P !== "string" ) throw new Error( "strings only" );
    return ( (P >>> 0).toString() ) === P && 
        ( P >>> 0 ) !== ( Math.pow( 2, 32 ) - 1 );
}

太好了!这就解释了为什么它没有被存储为索引,但是为什么我不能像看到“-1.2”一样看到它作为一个属性,当我可以检索它作为一个属性。 - akanksha1105
@akanksha1105 什么?console.log的输出在任何地方都没有规定。你根本不能信任它。看看console.log({splice: function(){}, length: 100, 50: 3})或者其他类似的东西。 - Esailija
@Esailija,akanksha1105想知道为什么console.log(a)没有列出键-1.2。编辑:没事了,你已经编辑了你的评论。 - Dave
2
要以相对合理的格式查看所有属性,请使用console.dir(array)命令。如果您的问题是关于console.log的特殊性(它可以执行任何随机操作),那么您应该明确说明,而不是询问名称和索引如何区分,这是一个已经定义好并且有答案的问题。@akanksha1105 - Esailija

1

首先需要知道的是,你的“数组”实际上更像是一个映射(有一些例外);

a = {}; // a is an empty object
a['02'] = 'foo';
a['2'] = 'bar';
console.log( a ); // { '02': 'foo', '2': 'bar' }

如果a是一个数组,同样适用,只是它的打印方式可能不同。例如,Google Chrome只会打印数组对象的类似数组的键(因此在这种情况下,您将看到[undefined,undefined,'bar'])。它仍然具有其他属性,但其“打印函数”只显示类似数组的属性。
还有其他差异。数组有某些属性,例如length(它将等于您定义的最大数值索引+1)和各种操作函数('pop','join'等)。
正如已经指出的那样,如果索引可以无损转换为无符号整数,则认为其类似于数字。即"2" -> 2 -> "2"不失任何内容,而"02" -> 2 -> "2"会丢失前导零。
要查看对象或数组中的所有内容,请使用以下内容(其中将显示您缺少的“02”键):
for(var i in a){
    console.log(i,a[i])
}

在第一行尝试使用 a = {splice: function(){}, length: 100}; :P - Esailija
我同意,但我的担忧有两个方面。首先,如果它将其存储为属性,为什么我看不到它,就像我可以看到“-1.2”一样,后者也是一个属性。其次,如果JavaScript正在尝试使用函数ToUint32(如Awake Zoldiek所述)将字符串转换为int,则>“02”>>> 0 //结果为2,因此它实际上应该将其存储在索引2处。 - akanksha1105
仅在可以无损转换时才将其转换为整数。即 02 -> 2,但 2 !-> 02,因此不适用。Esailija的代码很有趣。这是我不知道的一个微妙之处,我认为这一定与事物的打印方式有关。 - Dave
@akanksha1105 至于你的另一个问题,尝试这段代码:for(var i in a){console.log(i,a[i])},你会发现所有的属性都确实存在。 - Dave

0

在内部,JavaScript会在设置数组索引之前运行以下函数:

    function ToUint32(x) {
        return x >>> 0;
    }

因此,只有数字或字符串数字才是有效的数组索引。

索引还必须在以下范围内[0,2 ^ 32−1]。

如果一个字符串不是数字,则默认返回对象键。


这是不正确的,甚至会在问题中造成一半的混淆。这将为“02”返回2,但规范非常清楚,数字字符串中的前导零使其不符合数组索引的条件。 - Esailija

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