Что вызывает эту ошибку для for... in после назначения Array.prototype.indexOf?

Я был удивлен, когда смог воспроизвести ошибку с минимальным количеством кода. Обратите внимание, что в этом минималистском примере Array.indexOf не вызывается. Также обратите внимание, что я пробовал несколько различных реализаций indexOf, включая несколько из stackoverflow.com.

Ошибка в том, что когда for... in выполняется в IE, отображаются три предупреждения: "indexOf", "0" и "1". В FF, как и следовало ожидать, появляются только два ( "0", "1" ).

<html>
<body onLoad="test();">
<script language="javascript">
   var testArray = ['Foo', 'Bar'];

   if(!Array.prototype.indexOf) {
      Array.prototype.indexOf = function (obj, fromIndex) {
         if (fromIndex == null) {
            fromIndex = 0;
         } else if (fromIndex < 0) {
            fromIndex = Math.max(0, this.length + fromIndex);
         }
         for (var i = fromIndex, j = this.length; i < j; i++) {
            if (this[i] === obj)
               return i;
         }
         return -1;
      };
   }

   function test() {
      var i;

      for(i in testArray) {
         alert(i);
      }
   }
</script>
</body>
</html>

Кто-нибудь может это объяснить? Я уже изменил свой код, чтобы использовать while, поэтому я не под пистолетом, но на самом деле у меня все в тупике. Это напоминает мне ошибки переполнения памяти в c.

Ответ 1

См. " для интриги" на Yahoo! Блог пользовательского интерфейса.

Причина, по которой ваш код работает должным образом в Firefox, заключается в том, что вы не добавили свой собственный метод indexOf в Firefox. Цикл for in выполняет итерацию по всем ключам в цепочке прототипов объекта, включая добавленный метод indexOf. Дуглас Крокфорд предлагает следующее решение:

for (var p in testArray) {
    if (testArray.hasOwnProperty(p)) {
        alert(testArray[i]);
    }
}

В качестве альтернативы вы можете просто отфильтровать функции:

for (var p in testArray) {
    if (typeof testArray[p] !== "function") {
        alert(testArray[i]);
    }
}

Кроме того, как указывает "nickf", лучше не использовать цикл for in для итерации по массивам. Цикл for in предназначен для итерации по клавишам в объекте.

Стив

Ответ 2

for .. in предназначен для циклического переключения свойств объекта, а не массивов.

Придерживайтесь стандарта:

for (var i = 0, l = myArray.length; i < l; ++i) { .. }

Дополнительная информация в Центр разработчиков Mozilla:

Цикл

A for...in не перебирает встроенные свойства. К ним относятся все встроенные методы объектов, такие как метод String indexOf или метод Object toString. Тем не менее, цикл будет перебирать все пользовательские свойства (включая любые, которые перезаписывают встроенные свойства).

Хотя может быть соблазн использовать это как способ итерации по массиву, это плохая идея. Оператор for... in выполняет итерации над пользовательскими свойствами в дополнение к элементам массива, поэтому, если вы изменяете массив нецелого или неположительных свойств (например, добавляя к нему свойство "foo" или даже добавляя метод или свойство в Array.prototype), оператор for... в результате возвращает число ваших пользовательских свойств в дополнение к числовым индексам.