¿Qué significa el error JSLint 'cuerpo de un para en debe estar envuelto en una declaración if' significa?

242

Solía JSLint en un archivo JavaScript mío. Lanzó el error:

for( ind in evtListeners ) {

Problema en la línea 41, carácter 9: El cuerpo de un for in debe estar envuelto en una declaración if para filtrar las propiedades no deseadas del prototipo.

¿Qué significa esto?

jrharshath
fuente
55
Por defecto, 'in' itera sobre las propiedades heredadas también. Por lo general, el cuerpo está envuelto if (evtListeners.hasOwnProperty(ind))para restringir el procesamiento solo a las propiedades propias (no heredadas). Aún así, en algunos casos realmente desea iterar sobre todas las propiedades, incluidas las heredadas. En ese caso, JSLint lo obliga a ajustar el cuerpo del bucle en una declaración if para decidir qué propiedades realmente desea. Esto funcionará y hará feliz a JSlint: if (evtListeners[ind] !== undefined)
xorcus
1
La mayoría de las respuestas están desactualizadas. se puede encontrar una solución actualizada en stackoverflow.com/a/10167931/3138375
eli-bd

Respuestas:

430

En primer lugar, nunca use un for inbucle para enumerar sobre una matriz. Nunca. Usa lo bueno de siempre for(var i = 0; i<arr.length; i++).

La razón detrás de esto es la siguiente: cada objeto en JavaScript tiene un campo especial llamado prototype. Todo lo que agregue a ese campo será accesible en cada objeto de ese tipo. Suponga que desea que todas las matrices tengan una nueva función genial llamada filter_0que filtre los ceros.

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]

Esta es una forma estándar de extender objetos y agregar nuevos métodos. Muchas bibliotecas hacen esto. Sin embargo, veamos cómo for infunciona ahora:

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

¿Lo ves? De repente piensa que filter_0 es otro índice de matriz. Por supuesto, no es realmente un índice numérico, sino que se for inenumera a través de campos de objeto, no solo índices numéricos. Así que ahora estamos enumerando a través de cada índice numérico y filter_0 . Pero filter_0no es un campo de ningún objeto de matriz en particular, cada objeto de matriz tiene esta propiedad ahora.

Afortunadamente, todos los objetos tienen un hasOwnPropertymétodo que comprueba si este campo realmente pertenece al objeto en sí o si simplemente se hereda de la cadena de prototipos y, por lo tanto, pertenece a todos los objetos de ese tipo.

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

Tenga en cuenta que, aunque este código funciona como se espera para las matrices, nunca, nunca , debe usar for iny for each inpara las matrices. Recuerde que for inenumera los campos de un objeto, no los índices o valores de la matriz.

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
fuente
43
No debe usar for inpara iterar sobre matrices porque el lenguaje no asigna un orden en el que for inse enumerará sobre una matriz. Puede que no esté en orden numérico. Además, si usa la construcción de estilo `for (i = 0; i <array.length; i ++), puede estar seguro de que solo está iterando índices numéricos en orden y sin propiedades alfanuméricas.
Breton
¡Gracias! Guardaré esto como referencia.
nyuszika7h
Me encontré mirando esta respuesta nuevamente porque estaba convencido de que este fragmento de JSLint estaba roto. Tenía un código aproximado: for (o in listeners) {if (listeners.hasOwnProperty (i)) {console.log (o); }} El problema es que tuve un error, cambié los nombres de las variables i a o y omití una referencia. JSLint es lo suficientemente inteligente como para asegurarse de que está comprobando hasOwnProperty para la propiedad correcta en el objeto correcto.
2011
12
Sin embargo, en está bien iterar sobre la propiedad de un objeto. El OP nunca dijo que el for in se aplicara a una matriz. HasOwnProperty es la mejor práctica, sin embargo, hay casos en los que no lo desea, por ejemplo, si un objeto extiende a otro y desea enumerar tanto los objetos como las propiedades del 'padre'.
gotofritz
3
Creo que en lugar de ahuyentar a las personas de los for-inbucles (que son impresionantes, por cierto), deberíamos educarlos sobre cómo funcionan (hecho correctamente en esta respuesta) y presentarlos para Object.defineProperty()que puedan extender sus prototipos de manera segura sin romper nada. Por cierto, no se deben hacer prototipos de objetos nativos sin ellos Object.defineProperty.
Robert Rossmann
87

Douglas Crockford, el autor de jslint ha escrito (y hablado) sobre este tema muchas veces. Hay una sección en esta página de su sitio web que cubre esto:

para declaración

Una clase de declaraciones debe tener la siguiente forma:

for (initialization; condition; update) {
    statements
}

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

La primera forma debe usarse con matrices y con bucles de un número predeterminado de iteraciones.

La segunda forma debe usarse con objetos. Tenga en cuenta que los miembros que se agregan al prototipo del objeto se incluirán en la enumeración. Es aconsejable programar a la defensiva utilizando el método hasOwnProperty para distinguir los miembros verdaderos del objeto:

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

Crockford también tiene una serie de videos sobre el teatro YUI donde habla sobre esto. La serie de videos / charlas de Crockford sobre JavaScript son de visita obligada si usted es un poco serio con respecto a JavaScript.

Bretón
fuente
21

Malo: (jsHint arrojará un error)

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

Bueno:

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

La respuesta de Vava está en el blanco. Si usa jQuery, entonces la $.each()función se encarga de esto, por lo tanto, es más seguro de usar.

$.each(evtListeners, function(index, elem) {
    // your code
});
HRJ
fuente
55
Si el rendimiento es una consideración aquí, no recomendaría usar $.each(o underscore.js _.each) si puede salirse con la suya for. jsperf tiene algunas pruebas de comparación reveladoras que vale la pena ejecutar.
nickb
3
Esto ( jsperf.com/each-vs-each-vs-for-in/3 ) es más realista porque emplea el filtro de
protocolo
7

@todos: todo en JavaScript es un objeto (), por lo que las declaraciones como "solo usar esto en objetos" son un poco engañosas. Además, JavaScript no está fuertemente tipado para que 1 == "1" sea verdadero (aunque 1 === "1" no lo es, Crockford es grande en esto). Cuando se trata del concepto progromático de matrices en JS, la tipificación es importante en la definición.

@Brenton: no es necesario ser un dictador de terminología; "matriz asociativa", "diccionario", "hash", "objeto", todos estos conceptos de programación se aplican a una estructura en JS. Son pares de valores de nombre (clave, índice), donde el valor puede ser cualquier otro objeto (las cadenas también son objetos)

Entonces, new Array()es lo mismo que[]

new Object() es más o menos similar a {}

var myarray = [];

Crea una estructura que es una matriz con la restricción de que todos los índices (también conocidos como claves) deben ser un número entero. También permite la asignación automática de nuevos índices a través de .push ()

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

De hecho, se trata mejor a través de for(initialization;condition;update){

Pero que pasa:

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

Prueba esto:

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

Quizás no sea el mejor uso de una matriz, sino solo una ilustración de que las cosas no siempre son claras.

Si conoce sus claves, y definitivamente si no son números enteros, su única opción de estructura como matriz es el objeto.

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}
vadear
fuente
"Usar solo esto en objetos" significa no usarlo en matrices o cualquier otra cosa que extienda un Objeto, de lo contrario, como usted señala, sería muy tonto ya que todo extiende Objeto
Juan Mendes
'"matriz asociativa", "diccionario", "hash", "objeto", todos estos conceptos de programación se aplican a una estructura en JS'. No. Son conceptos diferentes de idiomas diferentes, con similitudes entre sí. Pero si se supone que son / exactamente iguales / y que se utilizarán de la misma manera, para los mismos fines, se está preparando para cometer algunos errores realmente estúpidos que podría evitar al ser menos ignorante sobre cómo el lenguaje Estás usando obras.
Breton
2

Seguramente es un poco extremo decir

... nunca use un bucle for in para enumerar sobre una matriz. Nunca. Use good old para (var i = 0; i <longitud de arr; i ++)

?

Vale la pena resaltar la sección en el extracto de Douglas Crockford

... La segunda forma debe usarse con objetos ...

Si necesita una matriz asociativa (también conocida como tabla hash / diccionario) donde se nombran las claves en lugar de indexarse ​​numéricamente, deberá implementar esto como un objeto, por ejemplo var myAssocArray = {key1: "value1", key2: "value2"...};.

En este caso myAssocArray.length, aparecerá nulo (porque este objeto no tiene una propiedad de 'longitud'), y i < myAssocArray.lengthno lo llevará muy lejos. Además de proporcionar una mayor comodidad, esperaría que las matrices asociativas ofrezcan ventajas de rendimiento en muchas situaciones, ya que las claves de la matriz pueden ser propiedades útiles (es decir, la propiedad o el nombre de identificación de un miembro de la matriz), lo que significa que no tiene que iterar a través de un largo matriz evaluando repetidamente si las declaraciones encuentran la entrada de matriz que está buscando.

De todos modos, gracias también por la explicación de los mensajes de error de JSLint, ¡usaré la verificación 'isOwnProperty' ahora cuando intente a través de mis innumerables matrices asociativas!

tonto
fuente
1
Estás profundamente confundido No hay tal cosa como "matrices asociativas" en javascript. Eso es estrictamente un concepto de PHP.
Breton
Es cierto que estos objetos no tienen una lengthpropiedad, pero puede hacerlo de otra manera:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h
3
@ Nyuszika7H Ese es el camino equivocado. Si no necesita la matriz indexada de enteros, no debe usarla var myArr = [], debe ser var myArr = {}en PHP, son lo mismo, pero no en JS.
Juan Mendes
La "matriz" asociativa no es una matriz.
Vincent McNabb
0

Solo para agregar al tema de for in / for / $. Each, agregué un caso de prueba jsperf para usar $ .each vs for in: http://jsperf.com/each-vs-for-in/2

Diferentes navegadores / versiones lo manejan de manera diferente, pero parece que $ .each y directamente son las opciones más baratas en términos de rendimiento.

Si está utilizando for in para iterar a través de una matriz / objeto asociativo, sabiendo lo que busca e ignorando todo lo demás, use $ .each si usa jQuery, o solo for in (y luego un descanso; una vez que haya alcanzó lo que sabe que debería ser el último elemento)

Si está iterando a través de una matriz para realizar algo con cada par de claves, debe usar el método hasOwnProperty si NO usa jQuery y usar $ .each si SÍ usa jQuery.

Sin for(i=0;i<o.length;i++)embargo, siempre use si no necesita una matriz asociativa ... lol chrome realizó eso un 97% más rápido que un for in o$.each

Benno
fuente