Was bedeutet der JSLint-Fehler "body of a for in sollte in eine if-Anweisung eingeschlossen werden"?

242

Ich habe JSLint für eine meiner JavaScript-Dateien verwendet. Es warf den Fehler:

for( ind in evtListeners ) {

Problem in Zeile 41, Zeichen 9: Der Text eines for in sollte in eine if-Anweisung eingeschlossen werden, um unerwünschte Eigenschaften aus dem Prototyp herauszufiltern.

Was bedeutet das?

jrharshath
quelle
5
Standardmäßig iteriert 'in' auch über geerbte Eigenschaften. Normalerweise wird der Body eingewickelt, if (evtListeners.hasOwnProperty(ind))um die Verarbeitung nur auf eigene (nicht vererbte) Eigenschaften zu beschränken. In einigen Fällen möchten Sie jedoch wirklich alle Eigenschaften durchlaufen, einschließlich der geerbten. In diesem Fall zwingt JSLint Sie, den Schleifenkörper in eine if-Anweisung zu packen, um zu entscheiden, welche Eigenschaften Sie wirklich möchten. Dies wird funktionieren und JSlint glücklich machen: if (evtListeners[ind] !== undefined)
Xorcus
1
Die meisten Antworten sind veraltet. Eine aktualisierte Lösung finden Sie unter stackoverflow.com/a/10167931/3138375
eli-bd

Antworten:

430

Zunächst einmal, nie eine Verwendung for inSchleife ein Array aufzählen über. Noch nie. Verwenden Sie gute alte for(var i = 0; i<arr.length; i++).

Der Grund dafür ist folgender: Jedes Objekt in JavaScript hat ein spezielles Feld namens prototype. Alles, was Sie diesem Feld hinzufügen, ist für jedes Objekt dieses Typs zugänglich. Angenommen, Sie möchten, dass alle Arrays eine coole neue Funktion haben filter_0, die Nullen herausfiltert.

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]

Dies ist eine Standardmethode zum Erweitern von Objekten und Hinzufügen neuer Methoden. Viele Bibliotheken tun dies. Schauen wir uns jedoch an, wie es for injetzt funktioniert:

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

Siehst du? Es denkt plötzlich, dass filter_0 ein weiterer Array-Index ist. Natürlich ist es nicht wirklich ein numerischer Index, sondern for inzählt durch Objektfelder auf, nicht nur durch numerische Indizes. Also zählen wir jetzt jeden numerischen Index auf und filter_0 . Ist filter_0aber kein Feld eines bestimmten Array-Objekts, hat jedes Array-Objekt jetzt diese Eigenschaft.

Glücklicherweise haben alle Objekte eine hasOwnPropertyMethode, die prüft, ob dieses Feld wirklich zum Objekt selbst gehört oder ob es einfach von der Prototypenkette geerbt wird und somit zu allen Objekten dieses Typs gehört.

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

Beachten Sie, dass dieser Code, obwohl er für Arrays wie erwartet funktioniert, niemals, niemals funktionieren sollte , Verwendung for inund for each infür Arrays. Denken Sie daran, dass for indie Felder eines Objekts aufgelistet werden, nicht Array-Indizes oder -Werte.

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
vava
quelle
43
Sie sollten nicht for inzum Durchlaufen von Arrays verwenden, da die Sprache nicht die Reihenfolge angibt, in der for indie Aufzählung über ein Array erfolgt. Möglicherweise ist es nicht in numerischer Reihenfolge. Wenn Sie außerdem das Konstrukt `for (i = 0; i <array.length; i ++) verwenden, können Sie sicher sein, dass Sie nur numerische Indizes in der angegebenen Reihenfolge und keine alphanumerischen Eigenschaften iterieren.
Breton
Vielen Dank! Ich werde dies als Referenz speichern.
Nyuszika7h
Ich sah mir diese Antwort erneut an, weil ich überzeugt war, dass dieses Stück JSLint kaputt war. Ich hatte Code, der ungefähr war: für (o in Listenern) {if (listeners.hasOwnProperty (i)) {console.log (o); }} Das Problem ist, dass ich einen Fehler hatte, die Variablennamen i auf o geändert und eine Referenz verpasst habe. JSLint ist intelligent genug, um sicherzustellen, dass Sie hasOwnProperty auf die richtige Eigenschaft für das richtige Objekt überprüfen.
Zeichnen
12
Es ist jedoch in Ordnung, die Eigenschaft eines Objekts zu durchlaufen. Das OP hat nie gesagt, dass das for in auf ein Array angewendet wurde. Die hasOwnProperty ist eine bewährte Methode. Es gibt jedoch Fälle, in denen Sie sie nicht möchten, z. B. wenn ein Objekt ein anderes erweitert und Sie sowohl die Objekte als auch die Eigenschaften des übergeordneten Objekts auflisten möchten.
Gotofritz
3
Ich denke, anstatt die Leute von for-inSchleifen abzuhalten (die übrigens fantastisch sind), sollten wir sie über ihre Funktionsweise informieren (in dieser Antwort richtig gemacht) und sie vorstellen, Object.defineProperty()damit sie ihre Prototypen sicher erweitern können, ohne etwas zu beschädigen. Im Übrigen sollte auf die Erweiterung der Prototypen nativer Objekte nicht verzichtet werden Object.defineProperty.
Robert Rossmann
87

Douglas Crockford, der Autor von jslint, hat viele Male über dieses Thema geschrieben (und gesprochen). Auf dieser Seite seiner Website gibt es einen Abschnitt, der Folgendes behandelt:

zur Erklärung

Eine for-Klasse von Anweisungen sollte die folgende Form haben:

for (initialization; condition; update) {
    statements
}

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

Die erste Form sollte mit Arrays und mit Schleifen einer vorbestimmbaren Anzahl von Iterationen verwendet werden.

Das zweite Formular sollte für Objekte verwendet werden. Beachten Sie, dass Elemente, die dem Prototyp des Objekts hinzugefügt werden, in die Aufzählung einbezogen werden. Es ist ratsam, defensiv zu programmieren, indem Sie die hasOwnProperty-Methode verwenden, um die wahren Elemente des Objekts zu unterscheiden:

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

Crockford hat auch eine Videoserie über das YUI-Theater, in der er darüber spricht. Crockfords Reihe von Videos / Vorträgen über Javascript ist ein Muss, wenn Sie Javascript überhaupt ernst nehmen.

Bretonisch
quelle
21

Schlecht: (jsHint gibt einen Fehler aus)

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

Gut:

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

Die Antwort von Vava ist genau richtig. Wenn Sie jQuery verwenden, $.each()kümmert sich die Funktion darum, daher ist die Verwendung sicherer.

$.each(evtListeners, function(index, elem) {
    // your code
});
HRJ
quelle
5
Wenn hier die Leistung eine Rolle spielt, würde ich nicht empfehlen, $.each(oder underscore.js _.each) zu verwenden, wenn Sie mit der Rohschleife davonkommen können for. jsperf hat einige augenöffnende Vergleichstests, die es wert sind, ausgeführt zu werden.
Nickb
3
Dies ( jsperf.com/each-vs-each-vs-for-in/3 ) ist realistischer, da der grundlegende Protofilter verwendet wird
dvdrtrgn
7

@all - alles in JavaScript ist ein Objekt (), daher sind Aussagen wie "Nur für Objekte verwenden" etwas irreführend. Außerdem ist JavaScript nicht stark typisiert, so dass 1 == "1" wahr ist (obwohl 1 === "1" nicht wahr ist, ist Crockford in dieser Hinsicht groß). Wenn es um das progressive Konzept von Arrays in JS geht, ist die Eingabe bei der Definition wichtig.

@Brenton - Sie müssen kein Terminologiediktator sein. "assoziatives Array", "Wörterbuch", "Hash", "Objekt", diese Programmierkonzepte gelten alle für eine Struktur in JS. Es handelt sich um Namenswertpaare (Schlüssel, Index), bei denen der Wert ein beliebiges anderes Objekt sein kann (Zeichenfolgen sind ebenfalls Objekte).

Also new Array()ist das gleiche wie[]

new Object() ist ungefähr ähnlich zu {}

var myarray = [];

Erstellt eine Struktur, die ein Array mit der Einschränkung ist, dass alle Indizes (auch als Schlüssel bezeichnet) eine ganze Zahl sein müssen. Es ermöglicht auch die automatische Zuweisung neuer Indizes über .push ()

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

Wird in der Tat am besten über behandelt for(initialization;condition;update){

Aber was ist mit:

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

Versuche dies:

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

Vielleicht nicht die beste Verwendung eines Arrays, sondern nur ein Beispiel dafür, dass die Dinge nicht immer eindeutig sind.

Wenn Sie Ihre Schlüssel kennen und definitiv keine ganzen Zahlen sind, ist das Objekt Ihre einzige Array-ähnliche Strukturoption.

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}
waten
quelle
"Nur für Objekte verwenden" bedeutet, dass Sie es nicht für Arrays oder andere Elemente verwenden, die Object erweitern. Andernfalls wäre es, wie Sie hervorheben, sehr dumm, da alles Object erweitert
Juan Mendes,
"Assoziatives Array", "Wörterbuch", "Hash", "Objekt", diese Programmierkonzepte gelten alle für eine Struktur in JS. " Nein. Es handelt sich um unterschiedliche Konzepte aus verschiedenen Sprachen mit Ähnlichkeiten untereinander. Wenn Sie jedoch davon ausgehen, dass sie / genau gleich / sind und auf die gleiche Weise für die gleichen Zwecke verwendet werden sollen, bereiten Sie sich darauf vor, einige wirklich dumme Fehler zu machen, die Sie vermeiden könnten, indem Sie die Sprache weniger ignorieren Sie verwenden Werke.
Bretonischer
2

Sicher ist es ein bisschen extrem zu sagen

... verwenden Sie niemals eine for in-Schleife, um über ein Array aufzulisten. Noch nie. Verwenden Sie gut alt für (var i = 0; i <arr.length; i ++)

?

Es lohnt sich, den Abschnitt im Douglas Crockford-Auszug hervorzuheben

... Die zweite Form sollte mit Objekten verwendet werden ...

Wenn Sie ein assoziatives Array (auch bekannt als Hashtable / Dictionary) benötigen, in dem Schlüssel anstelle von numerisch indiziert benannt werden, müssen Sie dies als Objekt implementieren, z var myAssocArray = {key1: "value1", key2: "value2"...};.

In diesem Fall myAssocArray.lengthwird null angezeigt (da dieses Objekt keine Eigenschaft 'length' hat), und Sie i < myAssocArray.lengthwerden nicht sehr weit kommen. Ich würde erwarten, dass assoziative Arrays nicht nur mehr Komfort bieten, sondern in vielen Situationen auch Leistungsvorteile bieten, da die Array-Schlüssel nützliche Eigenschaften sein können (dh die ID-Eigenschaft oder der Name eines Array-Mitglieds), sodass Sie nicht lange durchlaufen müssen Das Array prüft wiederholt, ob Anweisungen vorhanden sind, um den gewünschten Array-Eintrag zu finden.

Wie auch immer, danke auch für die Erklärung der JSLint-Fehlermeldungen, ich werde jetzt die 'isOwnProperty'-Prüfung verwenden, wenn ich über meine unzähligen assoziativen Arrays interagiere!

Tomfumb
quelle
1
Sie sind zutiefst verwirrt. In Javascript gibt es keine "assoziativen Arrays". Das ist streng genommen ein PHP-Konzept.
Breton
Es ist wahr, dass diese Objekte keine lengthEigenschaft haben, aber Sie können es auf andere Weise tun:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
Nyuszika7h
3
@ Nyuszika7H Das ist der falsche Weg. Wenn Sie das ganzzahlig indizierte Array nicht benötigen, sollten Sie es nicht verwenden var myArr = []. var myArr = {}In PHP sollten sie dasselbe sein, aber nicht in JS.
Juan Mendes
Das assoziative "Array" ist kein Array.
Vincent McNabb
0

Um das Thema for in / for / $. Zu ergänzen, habe ich einen jsperf-Testfall für die Verwendung von $ .each vs for in hinzugefügt: http://jsperf.com/each-vs-for-in/2

Verschiedene Browser / Versionen behandeln es unterschiedlich, aber es scheint, dass $ .each und direkt für in die leistungsmäßig günstigsten Optionen sind.

Wenn Sie for in verwenden, um ein assoziatives Array / Objekt zu durchlaufen, wissen, wonach Sie suchen, und alles andere ignorieren, verwenden Sie $ .each, wenn Sie jQuery verwenden, oder nur for in (und dann eine Pause; sobald Sie dies getan haben) erreicht, was Sie wissen, sollte das letzte Element sein)

Wenn Sie durch ein Array iterieren, um mit jedem Schlüsselpaar etwas auszuführen, sollten Sie die hasOwnProperty-Methode verwenden, wenn Sie jQuery NICHT verwenden, und $ .each verwenden, wenn Sie jQuery verwenden.

Immer verwenden, for(i=0;i<o.length;i++)wenn Sie kein assoziatives Array benötigen ... lol chrome hat das 97% schneller ausgeführt als ein for in oder$.each

Benno
quelle