Safari中出现负数数组索引的未解释行为

7

编辑:更简单的复制情况;以下代码:

setInterval(function(){
  var a=[10,20,30,40], i=-1;
  a[-1] = 42;
  while (i<10000) a[i++];
  console.log(a[-1],a[4294967295]);
},100);

...产生的输出:

     42 undefined
     undefined 42
     42 undefined
37x  undefined 42
     42 undefined
     undefined 42
     42 undefined
41x  undefined 42
     42 undefined
     undefined 42
     42 undefined

自己试试:http://jsfiddle.net/Fjwsg/


给定以下代码(或类似的代码(fiddle)):

<!DOCTYPE HTML>
<html><head><title>-1 Array Index</title></head><body>
  <label>p: <input id="p" size="3"></label>
  <script type="text/javascript">
  var p = document.getElementById('p');
  p.onkeyup = function(){
    var a = "10 20 30 40".split(/\s+/);
    foo(a, p.value*1);
  }

  function foo(a,p){
    var count=a.length, i=0, x;
    if (p) a[i=-1]=p;
    while (i<10000) x = a[i++ % count];
    console.dir(a);
  }
  </script>
</body></html>

当我聚焦在“p”输入框并键入1 backspace 2时,开发者控制台中出现以下内容:
Array has index of -1 on first pass, index of 4294967295 on third pass

一旦看到索引4294967295(2的32次方减1),有时会出现问题:有时开发工具会自动关闭,有时所有Safari选项卡都会冻结,需要重新启动才能恢复。
奇怪的是,如果我删除这个(在这种情况下基本上没用的)while循环,我就无法重现这个问题。而且我在Chrome或Firefox上也无法重现这个问题。
有人可以解释一下可能导致这个问题的根源吗? 这是在OS X 10.7.4上使用Safari 5.1.7时发生的。

在Windows 7机器上的Safari 5.1.4中无法重现Unable to reproduce。当我输入1时,长度为4,退格键也是长度为4,当我输入2时,长度仍然是4。它还将a[-1]设置为相应的按键上的1和2,这似乎是不可能的。我被难住了。 - Elliot Bonneville
在Safari 5.1.6 OS 10.7.4中,我得到了奇怪的索引,但是没有任何冻结。 - bfavaretto
1
如果您确实打算使用负索引,请将索引更改为字符串,并使用对象作为存储,而不是数组。 - Andrew
@Andrew 前者;我有一个数组需要循环遍历,但在第一次遍历时,我需要注入一个额外的值。我可以用另一种方法来解决这个问题,并且我会这样做,但这就是导致这个问题的原因。 - Phrogz
1
@Phrogz 我记不清在哪里读到的了,但是 Douglas Crockford 说 JavaScript 的 Array 实际上只是一个带有额外 length 属性的普通 Object,该属性始终比对象中最大的数值索引大 1,并且 JS 在查找和插入期间使用类型强制转换将数值索引更改为字符串。显然,在幕后发生了更多的事情,但这意味着如果您现在保留现有内容并切换到 {} 而不是 [],则不会损失太多。无论如何,在其他语言中,不能使用负索引,因为索引是指向堆的指针。 - Andrew
显示剩余7条评论
2个回答

3
使用负索引可能导致未定义的行为。
根据ECMAScript文档,只有当特定值p满足以下条件时,它才能是数组索引:
(p >>> 0 === p) && (p >>> 0 !== Math.pow(2, 32) - 1)

在你的情况下:
# -1 >>> 0
4294967295
# Math.pow(2, 32) - 1
4294967295

编辑

如果p未通过上述测试,则应将其视为常规对象属性,例如a.fooa['foo']。 话虽如此,事实证明,使用字符串转换设置负索引可以解决该问题。

a['' + (i =-1)] = p

结论:浏览器错误

感谢您提供的链接,但我认为规范使用的术语可能会让您感到困惑。在JavaScript中,像所有对象一样,数组可以在运行时添加任意数量的任意属性。规范只是限制哪些属性被视为“数组索引”,以便自动更新length属性。向数组添加属性foolastSeen"OMG!"是完全有效的。 - Phrogz
@Phrogz,a["-1"]和a[-1]之间有区别。您是在说JavaScript引擎应该将无效的索引转换为字符串,从而避免您看到的问题吗? - Ja͢ck
1
@Phrogz,你说的“导致意外行为”的问题是正确的。我已经更新了它。抱歉,英语不是我的母语,所以我想到的和写下来的并不总是一样的 :) - Ja͢ck
a[-1]="foo"; a["-1"]=="foo"; a["-2"]="bar"; a[-.2e1]=="bar" - Phrogz
var a = [1, 2, 3]; a[-1] = 'wat'; a.length == 3; for (var x in a) { console.log(a[x]); // 1, 2, 3, wat } … 我个人认为最好避免这样的写法。顺便问一下,您是否尝试将-1强制转换为字符串而运行相同的脚本? - Ja͢ck
显示剩余4条评论

2

我觉得这听起来像是JavaScriptCore中的一个错误。也许当第二次调用foo时,它被JIT编译,而JIT编译的代码引入了错误。

我建议您提交一个错误到JavaScriptCore,并附上您的测试用例。


感谢您的建议和可能的解释。我已将此文件归档为[错误#86733](https://bugs.webkit.org/show_bug.cgi?id=86733)。 - Phrogz

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