Что означает ошибка JSLint «тело для for должно быть заключено в оператор if»?

242

Я использовал JSLint для моего файла JavaScript. Выкинуло ошибку:

for( ind in evtListeners ) {

Проблема в строке 41, символ 9. Тело for in должно быть заключено в оператор if, чтобы отфильтровать нежелательные свойства из прототипа.

Что это значит?

jrharshath
источник
5
По умолчанию «in» выполняет итерации по наследуемым свойствам. Обычно тело оборачивается, if (evtListeners.hasOwnProperty(ind))чтобы ограничить обработку только собственными (не унаследованными) свойствами. Тем не менее, в некоторых случаях вы действительно хотите перебрать все свойства, включая унаследованные. В этом случае JSLint заставляет вас обернуть тело цикла в оператор if, чтобы решить, какие свойства вы действительно хотите. Это сработает и сделает JSlint счастливым: if (evtListeners[ind] !== undefined)
xorcus
1
Большинство ответов устарели. обновленное решение можно найти по адресу stackoverflow.com/a/10167931/3138375
eli-bd

Ответы:

430

Прежде всего, никогда не используйте for inцикл для перечисления по массиву. Никогда. Используйте старый добрый for(var i = 0; i<arr.length; i++).

Причина этого заключается в следующем: каждый объект в JavaScript имеет специальное поле с именем prototype. Все, что вы добавите в это поле, будет доступно для каждого объекта этого типа. Предположим, вы хотите, чтобы у всех массивов была новая классная функция, filter_0которая будет фильтровать нули.

Array.prototype.filter_0 = function() {
    var res = [];
    for (var i = 0; i < this.length; i++) {
        if (this[i] != 0) {
            res.push(this[i]);
        }
    }
    return res;
};

console.log([0, 5, 0, 3, 0, 1, 0].filter_0());
//prints [5,3,1]

Это стандартный способ расширения объектов и добавления новых методов. Многие библиотеки делают это. Однако давайте посмотрим, как for inработает сейчас:

var listeners = ["a", "b", "c"];
for (o in listeners) {
    console.log(o);
}
//prints:
//  0
//  1
//  2
//  filter_0

Ты видишь? Он вдруг думает, что filter_0 - это еще один индекс массива. Конечно, это на самом деле не числовой индекс, а for inперечисление через поля объекта, а не только числовые индексы. Итак, мы сейчас перечисляем все числовые индексы и filter_0 . Но filter_0это не поле какого-либо конкретного объекта массива, каждый объект массива теперь имеет это свойство.

К счастью, у всех объектов есть hasOwnPropertyметод, который проверяет, действительно ли это поле принадлежит самому объекту или оно просто унаследовано от цепочки прототипов и, следовательно, принадлежит всем объектам этого типа.

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}
 //prints:
 //  0
 //  1
 //  2

Обратите внимание, что, хотя этот код работает, как и ожидалось, для массивов, вы никогда не должны использовать никогдаfor in и for each inдля массивов. Помните, что for inперечисляет поля объекта, а не индексы или значения массива.

var listeners = ["a", "b", "c"];
listeners.happy = "Happy debugging";

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}

 //prints:
 //  0
 //  1
 //  2
 //  happy
Вава
источник
43
Не следует использовать for inдля итерации массивов, поскольку язык не гарантирует порядок, в котором for inбудет перечисляться массив. Это может быть не в порядке номеров. Кроме того, если вы используете стилевую конструкцию `for (i = 0; i <array.length; i ++), вы можете быть уверены, что выполняете только числовые индексы в порядке, а алфавитно-цифровые свойства отсутствуют.
Бретон,
Спасибо! Я сохраню это как ссылку.
nyuszika7h
Я снова посмотрел на этот ответ, потому что был уверен, что этот бит JSLint сломан. У меня был код, который был примерно: for (o в слушателях) {if (listeners.hasOwnProperty (i)) {console.log (o); }} Проблема в том, что у меня была ошибка, я поменял имена переменных i на o и пропустил ссылку. JSLint достаточно умен, чтобы убедиться, что вы проверяете hasOwnProperty для правильного свойства правильного объекта.
drewish
12
для in, однако, хорошо перебрать свойство объекта. ОП никогда не говорил, что for in был применен к массиву. HasOwnProperty - это лучшая практика, однако в некоторых случаях вы этого не хотите - например, если объект расширяет другой, и вы хотите перечислить как объекты, так и свойства «родительского» объекта.
получил
3
Я думаю, что вместо того, чтобы отпугивать людей от for-inциклов (что, кстати, здорово), мы должны научить их, как они работают (правильно сделано в этом ответе), и познакомить их с Object.defineProperty()тем, чтобы они могли безопасно расширять свои прототипы, ничего не нарушая. Кстати, расширение прототипов нативных объектов не должно обойтись без Object.defineProperty.
Роберт Россманн
87

Дуглас Крокфорд, автор jslint, писал (и говорил) об этой проблеме много раз. На этой странице его сайта есть раздел, который охватывает это:

для заявления

А для класса операторов должен иметь следующую форму:

for (initialization; condition; update) {
    statements
}

for (variable in object) {
    if (filter) {
        statements
    } 
}

Первая форма должна использоваться с массивами и с циклами заранее определенного числа итераций.

Вторая форма должна использоваться с объектами. Помните, что члены, добавленные в прототип объекта, будут включены в перечисление. Целесообразно программировать с помощью метода hasOwnProperty, чтобы различать истинных членов объекта:

for (variable in object) {
    if (object.hasOwnProperty(variable)) {
        statements
    } 
}

У Крокфорда также есть видеосериал о театре YUI, где он рассказывает об этом. Крокфордские серии видео / разговоров о javascript - это то, что нужно посмотреть, если вы хоть немного серьезно относитесь к javascript.

бретонский
источник
21

Плохо: (jsHint выдаст ошибку)

for (var name in item) {
    console.log(item[name]);
}

Хорошо:

for (var name in item) {
  if (item.hasOwnProperty(name)) {
    console.log(item[name]);
  }
}
Таро Алан
источник
8

Вава ответ на вопрос. Если вы используете jQuery, то $.each()функция позаботится об этом, поэтому безопаснее в использовании.

$.each(evtListeners, function(index, elem) {
    // your code
});
HRJ
источник
5
Если здесь важна производительность, я бы не рекомендовал использовать $.each(или underscore.js _.each), если вы можете избежать необработанного forцикла. У jsperf есть несколько сравнительных тестов , которые стоит запустить.
Ника
3
Это ( jsperf.com/each-vs-each-vs-for-in/3 ) более реалистично, поскольку в нем используется базовый фильтр прото
dvdrtrgn
7

@all - все в JavaScript является объектом (), поэтому такие выражения, как «использовать это только для объектов», немного вводят в заблуждение. Кроме того, JavaScript не является строго типизированным, поэтому 1 == «1» - это правда (хотя 1 === «1» - нет, Крокфорд в этом преуспел). Когда дело доходит до прогроматической концепции массивов в JS, в определении важна типизация.

@ Брентон - Не нужно быть диктатором терминологии; «ассоциативный массив», «словарь», «хэш», «объект», все эти концепции программирования применимы к одной структуре в JS. Это пары имя (ключ, индекс), где значением может быть любой другой объект (строки также являются объектами)

Итак, так new Array()же, как[]

new Object() примерно похож на {}

var myarray = [];

Создает структуру, которая является массивом с ограничением, что все индексы (или ключи) должны быть целыми числами. Это также позволяет автоматически назначать новые индексы с помощью .push ()

var myarray = ["one","two","three"];

Это действительно лучше всего решать с помощью for(initialization;condition;update){

Но что насчет:

var myarray = [];
myarray[100] = "foo";
myarray.push("bar");

Попробуй это:

var myarray = [], i;
myarray[100] = "foo";
myarray.push("bar");
myarray[150] = "baz";
myarray.push("qux");
alert(myarray.length);
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}

Возможно, не лучшее использование массива, а просто иллюстрация того, что вещи не всегда понятны.

Если вы знаете свои ключи и определенно, если они не являются целыми числами, ваш единственный вариант структуры массива - это объект.

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}
брод
источник
«Использовать это только на объектах» означает, что не используйте его на массивах или в других объектах, расширяющих Object, в противном случае, как вы отмечаете, это будет очень глупо, поскольку все расширяет Object
Хуан Мендес,
«ассоциативный массив», «словарь», «хэш», «объект», все эти концепции программирования применимы к одной структуре в JS. Нет. Это разные понятия разных языков, сходные между собой. Но если вы продолжаете предполагать, что они / абсолютно одинаковы / и будут использоваться одинаково для одних и тех же целей, вы настраиваете себя на совершение действительно глупых ошибок, которых вы могли бы избежать, будучи менее осведомленными о том, как язык вы используете работы.
Бретон
2

Конечно, это немного экстрим, чтобы сказать,

... никогда не используйте цикл for для перечисления массива. Никогда. Используйте старый добрый для (var i = 0; i <arr.length; i ++)

?

Стоит выделить раздел в выдержке Дугласа Крокфорда

... Вторая форма должна использоваться с объектами ...

Если вам требуется ассоциативный массив (он же hashtable / dictionary), в котором ключи именуются, а не численно индексируются, вам придется реализовать это как объект, например var myAssocArray = {key1: "value1", key2: "value2"...};.

В этом случае myAssocArray.lengthпридет ноль (потому что у этого объекта нет свойства 'length'), и вы i < myAssocArray.lengthне получите слишком далеко. В дополнение к обеспечению большего удобства, я ожидаю, что ассоциативные массивы обеспечат преимущества производительности во многих ситуациях, поскольку ключи массива могут быть полезными свойствами (т. Е. Свойством или именем идентификатора члена массива), то есть вам не нужно перебирать длинный массив повторно вычисляет операторы if, чтобы найти запись массива, которую вы ищете.

В любом случае, спасибо также за объяснение сообщений об ошибках JSLint, теперь я буду использовать проверку 'isOwnProperty' при работе с моими бесчисленными ассоциативными массивами!

tomfumb
источник
1
Вы глубоко смущены. В javascript нет такого понятия, как «ассоциативные массивы». Это строго концепция php.
Бретон
Это правда, что эти объекты не имеют lengthсвойства, но вы можете сделать это по-другому:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h
3
@ Nyuszika7H Это неправильный путь. Если вам не нужен массив с индексами целых чисел, вы не должны использовать var myArr = [], это должно быть var myArr = {}в PHP, это то же самое, но не в JS.
Хуан Мендес
Ассоциативный «массив» не является массивом.
Винсент Макнабб
0

Просто чтобы добавить к теме для in / for / $. Каждый, я добавил тестовый пример jsperf для использования $ .each против for in: http://jsperf.com/each-vs-for-in/2.

Разные браузеры / версии обрабатывают его по-разному, но кажется, что $ .each и сразу - самые дешевые варианты с точки зрения производительности.

Если вы используете for для перебора ассоциативного массива / объекта, зная, что вам нужно, и игнорируя все остальное, используйте $ .each, если вы используете jQuery, или просто for in (и затем разрыв; как только вы достиг того, что вы знаете, должен быть последним элементом)

Если вы перебираете массив для выполнения чего-либо с каждой парой ключей в нем, следует использовать метод hasOwnProperty, если вы НЕ используете jQuery, и использовать $ .each, если вы действительно используете jQuery.

Всегда используйте, for(i=0;i<o.length;i++)если вам не нужен ассоциативный массив, хотя ... LOL Chrome выполнил это на 97% быстрее, чем для In или$.each

Бенно
источник