Co oznacza błąd „JSLint” treści „for in” powinien być zawinięty w instrukcji if?


242

Użyłem JSLint na moim pliku JavaScript. Zgłoszył błąd:

for( ind in evtListeners ) {

Problem w linii 41 znak 9: Ciało for in powinno być owinięte w instrukcję if, aby odfiltrować niepożądane właściwości z prototypu.

Co to znaczy?


5
Domyślnie „in” iteruje również odziedziczone właściwości. Zwykle ciało jest owinięte, if (evtListeners.hasOwnProperty(ind))aby ograniczyć przetwarzanie tylko do własnych (nie odziedziczonych) właściwości. Jednak w niektórych przypadkach naprawdę chcesz iterować wszystkie właściwości, w tym te odziedziczone. W takim przypadku JSLint zmusza cię do zawinięcia treści pętli w instrukcji if, aby zdecydować, które właściwości naprawdę chcesz. To zadziała i sprawi, że JSlint będzie szczęśliwy: if (evtListeners[ind] !== undefined)
xorcus

1
Większość odpowiedzi jest nieaktualna. zaktualizowane rozwiązanie można znaleźć na stackoverflow.com/a/10167931/3138375
eli-bd

Odpowiedzi:


430

Przede wszystkim nigdy nie używaj for inpętli do wyliczania tablicy. Nigdy. Użyj starego dobrego for(var i = 0; i<arr.length; i++).

Przyczyna tego jest następująca: każdy obiekt w JavaScript ma specjalne pole o nazwie prototype. Wszystko, co dodasz do tego pola, będzie dostępne dla każdego obiektu tego typu. Załóżmy, że chcesz, aby wszystkie tablice miały nową, fajną funkcję filter_0, która odfiltruje zera.

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]

Jest to standardowy sposób rozszerzania obiektów i dodawania nowych metod. Robi to wiele bibliotek. Zobaczmy jednak, jak for interaz działa:

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

Czy ty widzisz? Nagle myśli, że filter_0 to kolejny indeks tablicy. Oczywiście nie jest to tak naprawdę indeks liczbowy, ale for inwylicza pola obiektowe, a nie tylko indeksy numeryczne. Wyliczamy teraz za pomocą każdego indeksu liczbowego i filter_0 . Ale filter_0nie jest polem żadnego konkretnego obiektu tablicy, każdy obiekt tablicy ma teraz tę właściwość.

Na szczęście wszystkie obiekty mają hasOwnPropertymetodę, która sprawdza, czy to pole naprawdę należy do samego obiektu, czy też jest po prostu dziedziczone z łańcucha prototypów, a zatem należy do wszystkich obiektów tego typu.

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

Zauważ, że chociaż ten kod działa zgodnie z oczekiwaniami dla tablic, nigdy nie powinieneś, nigdy nie używaj for ini for each indla tablic. Pamiętaj, że for inwylicza pola obiektu, a nie indeksy tablic lub wartości.

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
Nie należy używać for indo iteracji po tablicach, ponieważ język nie określa kolejności, w jakiej for inbędzie wyliczany w tablicy. Może nie być w kolejności numerycznej. Ponadto, jeśli użyjesz konstrukcji typu `for (i = 0; i <array.length; i ++), możesz być pewien, że iterujesz tylko indeksy liczbowe w kolejności i nie masz właściwości alfanumerycznych.
Breton

Dzięki! Zapiszę to jako odniesienie.
nyuszika7h

Ponownie spojrzałem na tę odpowiedź, ponieważ byłem przekonany, że ten fragment JSLint został zepsuty. Miałem mniej więcej kod: for (o in listeners) {if (listeners.hasOwnProperty (i)) {console.log (o); }} Problem polega na tym, że miałem błąd, zmieniłem nazwy zmiennych i na o i brakowało referencji. JSLint jest wystarczająco inteligentny, aby upewnić się, że sprawdzasz hasOwnProperty pod kątem poprawnej właściwości na właściwym obiekcie.
narysowany

12
w in można jednak iterować własność obiektu. OP nigdy nie powiedział, że for in zostało zastosowane do tablicy. HasOwnProperty to najlepsza praktyka, jednak zdarzają się przypadki, w których go nie chcesz - na przykład, gdy obiekt rozszerza inny, a chcesz wymienić zarówno obiekty, jak i właściwości „rodzica”.
gotofritz

3
Myślę, że zamiast odstraszyć ludzi od for-inpętli (które są świetne, nawiasem mówiąc), powinniśmy edukować ich, jak działają (właściwie zrobione w tej odpowiedzi) i wprowadzić ich, aby Object.defineProperty()mogli bezpiecznie rozszerzać prototypy, nie niszcząc niczego. Nawiasem mówiąc, rozszerzenie prototypów obiektów rodzimych nie powinno odbywać się bez Object.defineProperty.
Robert Rossmann

87

Douglas Crockford, autor jslint wiele razy pisał (i mówił) na ten temat. Na tej stronie jego witryny znajduje się sekcja, która obejmuje:

dla oświadczenia

Klasa A instrukcji powinna mieć następującą formę:

for (initialization; condition; update) {
    statements
}

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

Pierwszej formy należy używać z tablicami i pętlami o określonej liczbie iteracji.

Druga forma powinna być używana z obiektami. Należy pamiętać, że elementy dodane do prototypu obiektu zostaną uwzględnione w wyliczeniu. Mądrze jest programować defensywnie za pomocą metody hasOwnProperty, aby rozróżnić prawdziwe elementy obiektu:

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

Crockford ma także serię wideo na temat teatru YUI, w której mówi o tym. Seria filmów / rozmów Crockforda na temat javascript jest obowiązkowa, jeśli poważnie myślisz o javascript.


21

Źle: (jsHint zgłosi błąd)

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

Dobry:

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

8

Odpowiedź Vavy jest na znaku. Jeśli używasz jQuery, $.each()funkcja się tym zajmuje, dlatego bezpieczniej jest go używać.

$.each(evtListeners, function(index, elem) {
    // your code
});

5
Jeśli wydajność ma tutaj znaczenie, nie polecam używania $.each(lub underscore.js _.each), jeśli można uniknąć forpętli raw . jsperf ma kilka testów porównawczych, które warto uruchomić.
nickb

3
To ( jsperf.com/each-vs-each-vs-for-in/3 ) jest bardziej realistyczne, ponieważ wykorzystuje podstawowy filtr proto
dvdrtrgn

7

@all - wszystko w JavaScript jest obiektem (), więc stwierdzenia takie jak „używaj tego tylko na obiektach” są nieco mylące. Ponadto JavaScript nie jest mocno wpisany, więc 1 == „1” jest prawdziwe (chociaż 1 === „1” nie jest, Crockford jest na tym duży). Jeśli chodzi o progromatyczną koncepcję tablic w JS, pisanie jest ważne w definicji.

@Brenton - Nie trzeba być dyktatorem terminologii; „tablica asocjacyjna”, „słownik”, „skrót”, „obiekt”, wszystkie te koncepcje programowania dotyczą jednej struktury w JS. Jest to para nazwa-klucz (indeks, indeks), gdzie wartością może być dowolny inny obiekt (ciągi też są obiektami)

Więc new Array()jest taki sam jak[]

new Object() jest z grubsza podobny do {}

var myarray = [];

Tworzy strukturę, która jest tablicą z takim ograniczeniem, że wszystkie indeksy (inaczej klucze) muszą być liczbą całkowitą. Pozwala także na automatyczne przypisywanie nowych indeksów przez .push ()

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

Naprawdę najlepiej jest rozwiązać za pośrednictwem for(initialization;condition;update){

Ale co z:

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

Spróbuj tego:

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

Być może nie najlepsze użycie tablicy, ale tylko ilustracja, że ​​rzeczy nie zawsze są jednoznaczne.

Jeśli znasz swoje klucze, a na pewno nie są to liczby całkowite, jedyną opcją struktury podobnej do tablicy jest obiekt.

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}

„Używaj tego tylko na obiektach” oznacza, że ​​nie używaj go na tablicach lub czegokolwiek, co rozszerza Obiekt, w przeciwnym razie, jak zauważyłeś, byłoby to bardzo głupie, ponieważ wszystko rozszerza Obiekt
Juan Mendes

„„ tablica asocjacyjna ”,„ słownik ”,„ skrót ”,„ obiekt ”, wszystkie te koncepcje programowania dotyczą jednej struktury w JS.” Nie. Są różne koncepcje z różnych językach, z podobieństwa do siebie. Ale jeśli przyjmiesz założenie, że są one / dokładnie takie same / i będą używane w ten sam sposób, do tych samych celów, przygotujesz się na popełnienie naprawdę głupich błędów, których możesz uniknąć, będąc mniej nieświadomym, w jaki sposób język korzystasz z utworów.
Breton,

2

Z pewnością jest to trochę ekstremalne

... nigdy nie używaj pętli for in, aby wyliczyć tablicę. Nigdy. Użyj starej dobrej dla (var i = 0; i <arr.length; i ++)

?

Warto podkreślić sekcję w wyciągu Douglasa Crockforda

... Druga forma powinna być używana z obiektami ...

Jeśli potrzebujesz tablicy asocjacyjnej (aka hashtable / dictionary), w której klucze są nazywane zamiast indeksowanych numerycznie, będziesz musiał zaimplementować to jako obiekt, np var myAssocArray = {key1: "value1", key2: "value2"...};.

W takim przypadku myAssocArray.lengthpojawi się zero (ponieważ ten obiekt nie ma właściwości „length”), a i < myAssocArray.lengthnie zaprowadzisz cię daleko. Oprócz zapewnienia większej wygody spodziewałbym się, że tablice asocjacyjne będą oferować korzyści wydajnościowe w wielu sytuacjach, ponieważ klucze tablicy mogą być przydatnymi właściwościami (tj. Właściwością lub nazwą identyfikatora członka tablicy), co oznacza, że ​​nie musisz iterować przez długie tablica wielokrotnie oceniająca, czy instrukcje, aby znaleźć pozycję tablicy, której szukasz.

W każdym razie, dzięki również za wyjaśnienie komunikatów o błędach JSLint, użyję teraz sprawdzania „isOwnProperty” podczas interakcji z moimi niezliczonymi tablicami asocjacyjnymi!


1
Jesteś głęboko zdezorientowany. W javascript nie ma czegoś takiego jak „tablice asocjacyjne”. To jest koncepcja php.
Breton,

To prawda, że ​​te obiekty nie mają lengthwłaściwości, ale można to zrobić w inny sposób:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h

3
@ Nyuszika7H To niewłaściwy sposób. Jeśli nie potrzebujesz tablicy indeksowanej liczbami całkowitymi, nie powinieneś jej używać var myArr = [], powinno być var myArr = {}w PHP, są one tym samym, ale nie w JS.
Juan Mendes

„Tablica” asocjacyjna nie jest tablicą.
Vincent McNabb


0

Aby dodać do tematu for in / for / $. Każdy, dodałem przypadek testowy jsperf do używania $ .each vs for w: http://jsperf.com/each-vs-for-in/2

Różne przeglądarki / wersje radzą sobie z tym inaczej, ale wydaje się, że każdy z nich jest najtańszym rozwiązaniem pod względem wydajności.

Jeśli używasz in w celu iteracji przez asocjacyjną tablicę / obiekt, wiedząc, czego szukasz i ignorując wszystko inne, użyj $ .each, jeśli używasz jQuery, lub tylko dla in (a potem przerwa; po osiągnął, o czym wiesz, że powinien być ostatnim elementem)

Jeśli iterujesz po tablicy, aby wykonać coś z każdą parą kluczy, powinieneś użyć metody hasOwnProperty, jeśli NIE korzystasz z jQuery i użyj $ .each, jeśli używasz jQuery.

Zawsze używaj, for(i=0;i<o.length;i++)jeśli nie potrzebujesz tablicy asocjacyjnej, chociaż ... lol chrome wykonał to 97% szybciej niż w przypadku w lub$.each

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.