数组键号和“数字”意外地被认为是相同的

问题描述 投票:4回答:5

我一直在玩javascript数组,我遇到了,我觉得,有些不一致,我希望有人可以为我解释。

让我们从这开始:


var myArray = [1, 2, 3, 4, 5];
document.write("Length: " + myArray.length + "<br />");
for( var i in myArray){
   document.write( "myArray[" + i + "] = " + myArray[i] + "<br />");
}
document.write(myArray.join(", ") + "<br /><br />");
Length: 5
myArray[0] = 1
myArray[1] = 2
myArray[2] = 3
myArray[3] = 4
myArray[4] = 5
1, 2, 3, 4, 5

这段代码没有什么特别之处,但我知道javascript数组是一个对象,因此可能会向数组添加特性,这些特性添加到数组的方式似乎与我不一致。

在继续之前,让我注意如何在javascript中将字符串值转换为数字值。

  • 非空字符串 - >字符串或NaN的数字值
  • 空字符串 - > 0

因此,由于javascript数组是一个对象,以下是合法的:


myArray["someThing"] = "someThing";
myArray[""] = "Empty String";
myArray["4"] = "four";

for( var i in myArray){ document.write( "myArray[" + i + "] = " + myArray[i] + "<br />"); } document.write(myArray.join(", ") + "<br /><br />");

Length: 5
myArray[0] = 1
myArray[1] = 2
myArray[2] = 3
myArray[3] = 4
myArray[4] = four
myArray[someThing] = someThing
myArray[] = Empty String
1, 2, 3, 4, four

输出是意外的。

设置属性myArray [“4”]时,非空字符串“4”将转换为其数值,这似乎是正确的。但是,空字符串“”不会转换为其数字值0,它将被视为空字符串。此外,非空字符串“something”未转换为其数值NaN,它被视为字符串。那是哪个呢?是数字或字符串上下文中myArray []内的语句?

另外,为什么myArray.length和myArray.join(“,”)中不包含myArray的两个非数字属性?

javascript arrays type-conversion
5个回答
12
投票

JavaScript数组的键实际上是字符串。有关任意键的详细信息和地图类型的实现,请检查this answer


澄清并添加Jason发布的内容:JavaScript数组是对象。对象具有属性。属性名称是字符串值。因此,在更多事情发生之前,数组索引也会转换为字符串。如果以下属性(ECMA-262,15.4),属性名称P将被视为数组索引(即将调用特殊数组魔术):

ToString(ToUint32(P))等于P,ToUint32(P)不等于2 ^ 32 - 1

可以轻松验证数字索引将转换为字符串(而不是相反):

var array = [];
array[1] = 'foo';
array['1'] = 'bar';
array['+1'] = 'baz';
document.writeln(array[1]); // outputs bar

此外,使用for..in循环迭代数组条目的不良做法 - 如果有人搞砸了一些原型(并且它也不是很快),你可能会得到意想不到的结果。请改用标准的for(var i= 0; i < array.length; ++i)


2
投票

(编辑:以下不太正确)

JavaScript Object的键实际上是字符串。 Javascript数组本身具有数字索引。如果您存储具有可以解释为非负整数的索引的东西,它将尝试这样做。如果您存储的索引不是非负整数(例如,它是字母数字,负数或带小数部分的浮点数),它将在数组索引存储上失败,并默认为Object(这是Array的基类)存储,然后将参数转换为字符串并按字符串索引存储 - 但这些存储的属性不会被Array类看到,因此其方法/属性(长度,连接,切片,拼接)不可见,推,流行等)。

编辑:上面的内容不太正确(正如克里斯托弗的foo / bar / baz示例所示)。根据ECMAscript spec的实际存储索引实际上是字符串,但如果它们是有效的数组索引(非负整数),那么Array对象的[[Put]]方法是特殊的,使得这些特定值对于Array的“array-ish”可见方法。


2
投票

这是PhiLo's post的答案。他的基准是有缺陷的,因为他为对象版本使用不同的属性名称:他应该使用i而不是x

如果正确完成,例如:

var start, end, count = 1000000;

var obj = {},
    array = [];

start = new Date;
for(var i = count; i--; )
    array[i] = i;
end = new Date;
document.writeln(Number(end) - Number(start));

start = new Date;
for(var i = count; i--; )
    obj[i] = i;
end = new Date;
document.writeln(Number(end) - Number(start));

你会看到时间非常接近。在FF3.0.5中,阵列版本甚至一直较慢(在Opera中,它是相反的)。


0
投票

与JavaScript中的其他所有数据一样,数组是对象。对象已经被点符号祝福,以减轻开发人员的负担。用你的例子

myArray["someThing"] = "someThing";

和写作一样

myArray.someThing = "someThing";

在这种情况下,您将向对象添加属性,而不是将其添加到数组中。空字符串也一样,虽然你不能用点符号表示空字符串......奇怪,是吗?

在“4”的情况下,它可以被强制转换为整数,因此用作数组的索引。


0
投票

当他说“数组索引被转换为字符串”时,我不同意Christoph。

首先,我认为它是依赖于实现的...我认为(好的)实现者将优化阵列访问,有一些聪明的方法可以做到这一点。

实际上,我做了一点测试,虽然它和大多数微基准测试一样好(即不是超级可靠),但它很有趣:

result = ""
var x;

var trueArray = []
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "i" + i; // To do the same operations
  trueArray[i] = 1;
}
var endTime = new Date();
result += "With array: " + (endTime - startTime) + "\n";

var sArray = []
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "" + i;
  sArray[x] = 1;
}
var endTime = new Date();
result += "With s array: " + (endTime - startTime) + "\n";

var objArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "i" + i;
  objArray[x] = 1;
}
var endTime = new Date();
result += "With object(i): " + (endTime - startTime) + "\n";

var sobjArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "" + i;
  sobjArray[x] = 1;
}
var endTime = new Date();
result += "With s object: " + (endTime - startTime) + "\n";

var iobjArray = {}
var startTime = new Date();
for (var i = 0; i < 100000; i++)
{
  x = "" + i;
  iobjArray[i] = 1;
}
var endTime = new Date();
result += "With i object: " + (endTime - startTime) + "\n";


// Then display result

在IE6上,我得到:使用数组:1453使用对象:3547 在FF 3.0上,我得到:使用数组:83使用对象:226 在Safari 3.1上,我得到:使用数组:140对象:313 在Opera 9.26上,由于某些原因我没有得到结果,但如果我减少到循环数的十分之一,我得到:使用数组:47对象:516 实际上,当我输入这个时,我让Opera运行,最后得到了结果:数组:281对象:166063 ...

因此阵列得到优化!哪个是幸运的...... 克里斯托夫的示威并没有让我印象深刻。我的结论更多的是,可以解释为数字的字符串被视为这样,这与引用的公式一致......

所以我对你的结果的解释是,当使用这些数据时,数组表现得像一个带有数字索引的快速数组(可能是稀疏值上的关联数组行为,即一些孤立的大指数),但作为一个对象,它仍然是具有正常的属性处理。但是这些属性不在数组部分中处理,因此使用join()得到的结果。

[编辑]我按照克里斯托夫的想法添加了一些循环。 在FF3上,我得到:使用数组:92使用s数组:93使用对象(i):243使用s对象:194使用i对象:125(穿孔在运行之间变化,但大致一致)。

我并不十分相信这个整数 - >字符串 - >整数往返,甚至不是ECMA请求这个序列。我读它的方式是:属性是一个字符串,可以解释为整数,然后它被视为这样。

当然,唯一可靠的方法是查看实现......

我感兴趣地注意到,获取整数属性的普通对象或可以转换为整数的属性以某种方式进行了优化。也许是因为许多JS程序员使用普通对象作为数组,所以实施者认为有兴趣优化这种情况。

© www.soinside.com 2019 - 2024. All rights reserved.