数组键 number 和 "number" 意外地被认为是相同的

Posted

技术标签:

【中文标题】数组键 number 和 "number" 意外地被认为是相同的【英文标题】:Arrays keys number and "number" are unexpectedly considered the same 【发布时间】:2010-10-02 00:16:49 【问题描述】:

我一直在玩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 />");
长度:5 我的数组 [0] = 1 我的数组 [1] = 2 我的数组 [2] = 3 我的数组 [3] = 4 我的数组 [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 />");

长度:5 我的数组 [0] = 1 我的数组 [1] = 2 我的数组 [2] = 3 我的数组 [3] = 4 myArray[4] = 四 myArray[someThing] = someThing myArray[] = 空字符串 1、2、3、4、4

输出是意外的。

在设置属性myArray["4"]时,非空字符串“4”被转换成它的数值,这看起来是对的。但是,空字符串“”不会转换为它的数值,0,它被视为空字符串。此外,非空字符串“something”不会转换为其数值 NaN,它被视为字符串。那么它是哪一个? myArray[] 中的语句是在数字还是字符串上下文中?

另外,为什么 myArray 的两个非数字属性没有包含在 myArray.length 和 myArray.join(", ") 中?

【问题讨论】:

【参考方案1】:

我不同意 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运行,最后得到结果:With array: 281 With object: 166063...

所以数组被优化了!这是幸运的... Christoph 的演示并没有给我留下深刻印象。我的结论是更多可以解释为数字的字符串被视为数字,这与引用的公式一起......

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

[编辑] 我按照 Christoph 的想法添加了一些循环。 在 FF3 上,我得到: 使用数组:92 使用 s 数组:93 使用对象(i):243 使用 s 对象:194 使用 i 对象:125(性能在运行之间有所不同,但大致一致)。

我不太相信这个整数 -> 字符串 -> 整数往返,甚至不是 ECMA 请求这个序列。我读的方式是:是不是属性是一个字符串,可以解释为整数,那么就这样处理。

当然,唯一确定的方法是查看实现...

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

【讨论】:

再次阅读我的答案:数字索引特殊处理;但在此之前,它们将被视为常规属性名称,即字符串!因此,如果您执行数组 [1] 之类的操作,它将变为数字->字符串(因为它是一个属性名称),然后再次变为字符串->数字(因为数组魔术) 而且它不依赖于实现:检查 ECMA-262!只有数组的实现是未指定的,而不是它们的语义属性! “我不太相信这个整数 -> 字符串 -> 整数往返” - 自然,实现可以优化这一点 - 但标准要求它们表现得好像它们不会 ECMA-262 的相应部分:15.4(第 100 页)和 15.4.5.1(第 109 页) 特别注意 15.4.5.1 的第 7 步及以下:数组魔法发生在索引用作常规属性名称之后 - 这意味着字符串!【参考方案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 中则相反)。

【讨论】:

感谢您对其他测试的评论和想法。我写了我的答案作为我的消息的编辑。现在,重要的结论是您对数组循环的评论,以及它们针对整数索引进行优化的事实。剩下的就是细节,不是吗? :-)【参考方案3】:

数组,就像 JavaScript 中的所有其他东西一样,都是对象。对象被赋予了点符号以减轻开发者的负担。用你的例子

myArray["someThing"] = "someThing";

和写一样

myArray.someThing = "someThing";

在这种情况下,您将属性添加到对象而不是将其添加到数组中。空字符串也是如此,尽管您不能将 点表示法 用于空字符串...很奇怪,对吧?

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

【讨论】:

【参考方案4】:

(编辑:以下不太正确)

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

编辑:以上内容并不完全正确(如 Christopher 的 foo/bar/baz 示例所示)。根据ECMAscript spec 的实际存储索引实际上是字符串,但如果它们是有效的数组索引(非负整数),则 Array 对象的特殊方法 [[Put]] 使这些特定值对 Array 的 "数组式”的方法。

【讨论】:

【参考方案5】:

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 &lt; array.length; ++i)

【讨论】:

我们都是对的。它们是字符串,但数字索引以特殊方式解释。 @Jason:Nitpick:标准规定如果 ToString(ToUint32(P)) 等于 P,则对属性名称 P 进行特殊处理。属性名称本身是一个字符串值,所以我支持我的声明 吹毛求疵:我的“我们都是对的”旨在适用于您和我的答案。你是对的,我的“这不是很正确”的说法并不完全正确。 :) @Jason:好的。我还添加了一个小解释(应该是正确的,还是我歪曲了什么?);如果每个人都是对的,我们就可以从此过上幸福的生活;) 看起来不错,你赢了;) +1 用于 foo/bar/baz 示例。 (同情点总是受欢迎的......)

以上是关于数组键 number 和 "number" 意外地被认为是相同的的主要内容,如果未能解决你的问题,请参考以下文章

为啥插入子文档数组时出现“重复键错误”?

为啥插入子文档数组时出现“重复键错误”?

safari input type="number"

二维数组相同键里的值相加

在 Chrome 中不再允许 input type="number" 上的 selectionStart/selectionEnd

AJAX POST提交number数组数据的请求可php的到的是string数组