Що означає тест помилки JSLint «тіло для for в, яке слід загорнути у вислів if»?


242

Я використовував JSLint у файлі JavaScript. Він кинув помилку:

for( ind in evtListeners ) {

Проблема в рядку 41 символу 9: Тіло для вхідного документа повинно бути загорнене в оператор if для фільтрації небажаних властивостей з прототипу.

Що це означає?


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 на правильність властивості правильного об’єкта.
веселий

12
оскільки в, однак, чудово перебирати властивість об'єкта. ОП ніколи не говорив, що для масиву застосовано "в". Практика hasOwnProperty є найкращою практикою, проте є випадки, коли ви цього не хочете - наприклад, якщо об’єкт розширює інший, і ви хочете перелічити як об'єкти, так і властивості "батьківського".
gotofritz

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
});

5
Якщо тут є врахування продуктивності, я б не рекомендував використовувати $.each(або underscore.js _.each), якщо ви можете піти з непереробленого forциклу. jsperf має кілька відкриваючих очей тестів порівняння , які варто запустити.
nickb

3
Це ( jsperf.com/each-vs-each-vs-for-in/3 ) є більш реалістичним, оскільки в ньому використовується базовий протофільтр
dvdrtrgn

7

@all - все в JavaScript є об'єктом (), тому заяви типу "використовувати це лише для об'єктів" трохи вводять в оману. Крім того, JavaScript не сильно набраний, щоб 1 == "1" було істинним (хоча 1 === "1" не є, Крокфорд великий у цьому). Якщо мова йде про прогроматичну концепцію масивів у JS, то типізація важлива у визначенні.

@Brenton - Не потрібно бути термінологічним диктатором; "асоціативний масив", "словник", "хеш", "об'єкт", ці концепції програмування застосовуються до однієї структури 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]);
    }
}

"Використовувати це лише для об'єктів" означає, що не використовуйте його на масивах чи іншому, що розширює "Об'єкт", інакше, як ви зазначаєте, це було б дуже нерозумно, оскільки все поширюється на "Об'єкт"
Хуан Мендес

'"асоціативний масив", "словник", "хеш", "об'єкт", ці концепції програмування застосовуються до однієї структури JS. " Ні. Це різні поняття з різних мов, с схожі між собою. Але якщо ви припускаєте, що вони / абсолютно однакові / і використовуються однаково, для тих же цілей, ви налаштовуєте себе на те, щоб зробити деякі справді дурні помилки, яких ви могли б уникнути, будучи менш неосвіченими щодо того, як мова ти використовуєш твори.
Бретон

2

Безумовно, це трохи екстремально сказати

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

?

Варто виділити розділ у витязі Дугласа Крокфорда

... Другу форму слід використовувати з об'єктами ...

Якщо вам потрібен асоціативний масив (він же хештеб / словник), де ключі називаються замість числово індексованих, вам доведеться реалізувати це як об'єкт, наприклад var myAssocArray = {key1: "value1", key2: "value2"...};.

У цьому випадку myAssocArray.lengthвийде недійсне (оскільки у цього об’єкта немає властивості "length"), і ви i < myAssocArray.lengthне отримаєте вас дуже далеко. На додаток до більшої зручності, я б очікував, що асоціативні масиви пропонують переваги у ефективності у багатьох ситуаціях, оскільки ключі масиву можуть бути корисними властивостями (тобто властивістю ідентифікатора або масиву члена масиву), тобто не потрібно повторювати через довгий масив багаторазово оцінює, чи є заяви для пошуку запису масиву, який ви шукаєте.

У будь-якому випадку, дякую також за пояснення повідомлень про помилки JSLint, я зараз використовуватиму перевірку 'isOwnProperty' під час взаємодії через безліч асоціативних масивів!


1
Ви глибоко розгублені. У JavaScript не існує такого поняття, як "асоціативні масиви". Це суворо концепція php.
Бретон

Це правда, що ці об’єкти не мають lengthвластивості, але ви можете це зробити іншим способом:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h

3
@ Nyuszika7H Це неправильний шлях. Якщо вам не потрібен цілочисельний індексований масив, ви не повинні використовувати var myArr = [], це повинно бути var myArr = {}в PHP, вони те саме, але не в JS.
Хуан Мендес

Асоціативний "масив" не є масивом.
Вінсент Макнабб


0

Просто для того, щоб додати тему для в / для / $. Я додав тестовий випадок jsperf для використання $ .each vs for in: http://jsperf.com/each-vs-for-in/2

Різні браузери / версії обробляють це по-різному, але здається, що $ .each і прямо зараз - це найдешевші варіанти, залежні від продуктивності.

Якщо ви використовуєте для ітерації через асоціативний масив / об’єкт, знаючи, що ви шукаєте, і ігноруючи все інше, використовуйте $ .each, якщо ви використовуєте jQuery, або просто для в (а потім перерва; як тільки ви дійшли до того, що, на вашу думку, має бути останнім елементом)

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

Завжди використовуйте, for(i=0;i<o.length;i++)якщо вам не потрібен асоціативний масив, хоча ... lol chrome виконано на 97% швидше, ніж для в$.each

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.