Que signifie l'erreur JSLint «le corps d'un for in doit être enveloppé dans une instruction if»?

242

J'ai utilisé JSLint sur un de mes fichiers JavaScript. Il a jeté l'erreur:

for( ind in evtListeners ) {

Problème à la ligne 41, caractère 9: le corps d'un for in doit être encapsulé dans une instruction if pour filtrer les propriétés indésirables du prototype.

Qu'est-ce que ça veut dire?

jrharshath
la source
5
Par défaut, «in» parcourt également les propriétés héritées. Habituellement, le corps est encapsulé if (evtListeners.hasOwnProperty(ind))pour limiter le traitement uniquement aux propriétés propres (non héritées). Pourtant, dans certains cas, vous voulez vraiment parcourir toutes les propriétés, y compris celles héritées. Dans ce cas, JSLint vous oblige à encapsuler le corps de la boucle dans une instruction if pour décider quelles propriétés vous voulez vraiment. Cela fonctionnera et rendra JSlint heureux: if (evtListeners[ind] !== undefined)
xorcus
1
La plupart des réponses sont obsolètes. une solution mise à jour peut être trouvée à stackoverflow.com/a/10167931/3138375
eli-bd

Réponses:

430

Tout d'abord, n'utilisez jamais defor in boucle pour énumérer un tableau. Jamais. Utilisez du bon vieux for(var i = 0; i<arr.length; i++).

La raison derrière cela est la suivante: chaque objet en JavaScript a un champ spécial appelé prototype. Tout ce que vous ajoutez à ce champ sera accessible sur tous les objets de ce type. Supposons que vous vouliez que tous les tableaux aient une nouvelle fonction cool appelée filter_0qui filtrera les zéros.

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]

Il s'agit d'une méthode standard pour étendre des objets et ajouter de nouvelles méthodes. De nombreuses bibliothèques le font. Cependant, regardons comment for infonctionne maintenant:

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

Est-ce que tu vois? Il pense soudainement que filter_0 est un autre index de tableau. Bien sûr, ce n'est pas vraiment un index numérique, mais il for inénumère les champs d'objet, pas seulement les index numériques. Nous énumérons donc maintenant chaque index numérique et filter_0 . Mais filter_0n'est pas un champ d'un objet tableau particulier, chaque objet tableau a maintenant cette propriété.

Heureusement, tous les objets ont une hasOwnPropertyméthode qui vérifie si ce champ appartient vraiment à l'objet lui-même ou s'il est simplement hérité de la chaîne prototype et appartient donc à tous les objets de ce type.

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

Notez que bien que ce code fonctionne comme prévu pour les tableaux, vous ne devez jamais, jamais utiliser for inet for each inpour les tableaux. N'oubliez pas for inqu'énumère les champs d'un objet, pas les index de tableau ou les valeurs.

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
la source
43
Vous ne devez pas utiliser for inpour parcourir les tableaux car le langage ne garantit pas l'ordre dans lequel for insera énuméré sur un tableau. Ce n'est peut-être pas dans l'ordre numérique. De plus, si vous utilisez le `for (i = 0; i <array.length; i ++) construction de style, vous pouvez être sûr que vous n'itérer les index numériques dans l' ordre, et pas de propriétés alphanumériques.
Breton
Merci! Je vais enregistrer cela comme référence.
nyuszika7h
Je me suis retrouvé à revoir cette réponse car j'étais convaincu que ce morceau de JSLint était cassé. J'avais du code qui était à peu près: pour (o dans les écouteurs) {if (listeners.hasOwnProperty (i)) {console.log (o); }} Le problème est que j'avais un bug, j'avais changé les noms de variables i en o et manqué une référence. JSLint est suffisamment intelligent pour vous assurer que vous vérifiez hasOwnProperty pour la bonne propriété sur l'objet correct.
drewish
12
car in, cependant, est bien pour itérer sur la propriété d'un objet. L'OP n'a jamais dit que le for in était appliqué à un tableau. Le hasOwnProperty est la meilleure pratique, cependant il y a des cas où vous ne le voulez pas - par exemple si un objet en étend un autre, et vous voulez lister à la fois les objets et les propriétés du 'parent'.
gotofritz
3
Je pense qu'au lieu d'effrayer les gens des for-inboucles (qui sont géniales, soit dit en passant), nous devrions leur expliquer comment ils fonctionnent (fait correctement dans cette réponse) et les leur présenter Object.defineProperty()afin qu'ils puissent étendre leurs prototypes en toute sécurité sans rien casser. Incidemment, l'extension des prototypes d'objets natifs ne devrait pas se faire sans Object.defineProperty.
Robert Rossmann
87

Douglas Crockford, l'auteur de jslint a écrit (et parlé) à ce sujet à plusieurs reprises. Il y a une section sur cette page de son site Web qui couvre ceci:

pour la déclaration

Une classe d'instructions doit avoir la forme suivante:

for (initialization; condition; update) {
    statements
}

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

Le premier formulaire doit être utilisé avec des tableaux et avec des boucles d'un nombre prédéterminé d'itérations.

Le deuxième formulaire doit être utilisé avec des objets. N'oubliez pas que les membres ajoutés au prototype de l'objet seront inclus dans l'énumération. Il est sage de programmer de manière défensive en utilisant la méthode hasOwnProperty pour distinguer les vrais membres de l'objet:

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

Crockford a également une série de vidéos sur le théâtre YUI où il en parle. La série de vidéos / discussions sur javascript de Crockford est un must si vous êtes même un peu sérieux au sujet de javascript.

Breton
la source
21

Mauvais: (jsHint générera une erreur)

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

Bien:

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

La réponse de Vava est dans le mille. Si vous utilisez jQuery, la $.each()fonction s'en charge, il est donc plus sûr de l'utiliser.

$.each(evtListeners, function(index, elem) {
    // your code
});
HRJ
la source
5
Si les performances sont prises en compte ici, je ne recommanderais pas d'utiliser $.each(ou underscore.js _.each) si vous pouvez vous en sortir avec la forboucle brute . jsperf a quelques tests de comparaison révélateurs qui valent la peine d'être exécutés.
nickb
3
Ceci ( jsperf.com/each-vs-each-vs-for-in/3 ) est plus réaliste car il utilise le filtre proto de base
dvdrtrgn
7

@all - tout en JavaScript est un objet (), donc les instructions comme "n'utiliser ceci que sur des objets" sont un peu trompeuses. De plus, JavaScript n'est pas fortement typé de sorte que 1 == "1" soit vrai (bien que 1 === "1" ne le soit pas, Crockford est très important à ce sujet). En ce qui concerne le concept progromatique de tableaux dans JS, le typage est important dans la définition.

@Brenton - Pas besoin d'être un dictateur terminologique; "tableau associatif", "dictionnaire", "hachage", "objet", ces concepts de programmation s'appliquent tous à une structure dans JS. Il s'agit de paires de valeurs de nom (clé, index), où la valeur peut être n'importe quel autre objet (les chaînes sont également des objets)

Donc, new Array()c'est la même chose que[]

new Object() est à peu près similaire à {}

var myarray = [];

Crée une structure qui est un tableau avec la restriction que tous les index (aka clés) doivent être un nombre entier. Il permet également l'attribution automatique de nouveaux index via .push ()

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

Est en effet mieux géré via for(initialization;condition;update){

Mais qu'en est-il:

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

Essaye ça:

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

Peut-être pas la meilleure utilisation d'un tableau, mais juste une illustration que les choses ne sont pas toujours claires.

Si vous connaissez vos clés, et certainement si ce ne sont pas des nombres entiers, votre seule option de structure de type tableau est l'objet.

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}
patauger
la source
«N'utiliser ceci que sur des objets» signifie ne pas l'utiliser sur des tableaux ou tout autre élément qui étend l'objet, sinon, comme vous le faites remarquer, ce serait très idiot puisque tout étend l'objet
Juan Mendes
«« tableau associatif »,« dictionnaire »,« hachage »,« objet », ces concepts de programmation s'appliquent tous à une structure dans JS.» Non. Ce sont des concepts différents de différentes langues, avec des similitudes les uns avec les autres. Mais si vous supposez qu'ils sont / exactement les mêmes / et qu'ils doivent être utilisés de la même manière, aux mêmes fins, vous vous préparez à commettre des erreurs vraiment stupides que vous pourriez éviter en étant moins ignorant de la façon dont la langue vous utilisez des œuvres.
Breton
2

C'est sûrement un peu extrême de dire

... n'utilisez jamais une boucle for in pour énumérer un tableau. Jamais. Utilisez bon vieux pour (var i = 0; i <arr.length; i ++)

?

Il convient de souligner la section de l'extrait de Douglas Crockford

... Le deuxième formulaire doit être utilisé avec des objets ...

Si vous avez besoin d'un tableau associatif (aka table de hachage / dictionnaire) où les clés sont nommées au lieu d'être indexées numériquement, vous devrez l'implémenter comme un objet, par exemple var myAssocArray = {key1: "value1", key2: "value2"...};.

Dans ce cas, le résultat myAssocArray.lengthsera nul (car cet objet n'a pas de propriété 'length'), et vous i < myAssocArray.lengthn'irez pas très loin. En plus de fournir une plus grande commodité, je m'attendrais à ce que les tableaux associatifs offrent des avantages de performances dans de nombreuses situations, car les clés de tableau peuvent être des propriétés utiles (c.-à-d. La propriété ou le nom d'ID d'un membre du tableau), ce qui signifie que vous n'avez pas à parcourir une longue tableau évaluant à plusieurs reprises si les instructions pour trouver l'entrée de tableau que vous recherchez.

Quoi qu'il en soit, merci également pour l'explication des messages d'erreur JSLint, j'utiliserai maintenant la vérification «isOwnProperty» lors de l'interaction via ma myriade de tableaux associatifs!

tomfumb
la source
1
Vous êtes profondément confus. Il n'y a pas de "tableaux associatifs" en javascript. C'est strictement un concept php.
Breton
C'est vrai que ces objets n'ont pas de lengthpropriété, mais vous pouvez le faire d'une autre manière:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h
3
@ Nyuszika7H C'est la mauvaise façon. Si vous n'avez pas besoin du tableau indexé entier, vous ne devriez pas l'utiliser var myArr = [], il devrait être var myArr = {}en PHP, c'est la même chose, mais pas en JS.
Juan Mendes
Le "tableau" associatif n'est pas un tableau.
Vincent McNabb
0

Juste pour ajouter au sujet de for in / for / $. Each, j'ai ajouté un cas de test jsperf pour utiliser $ .each vs for in: http://jsperf.com/each-vs-for-in/2

Différents navigateurs / versions le gèrent différemment, mais il semble que $ .each et directement pour soient les options les moins chères en termes de performances.

Si vous utilisez for in pour parcourir un tableau / objet associatif, sachant ce que vous recherchez et ignorant tout le reste, utilisez $ .each si vous utilisez jQuery, ou simplement for in (puis une pause; une fois que vous avez atteint ce que vous savez devrait être le dernier élément)

Si vous parcourez un tableau pour effectuer quelque chose avec chaque paire de clés, utilisez la méthode hasOwnProperty si vous N'utilisez PAS jQuery et utilisez $ .each si vous utilisez jQuery.

Utilisez toujours for(i=0;i<o.length;i++)si vous n'avez pas besoin d'un tableau associatif ... lol chrome a effectué 97% plus rapidement qu'un for in ou$.each

Benno
la source