iterar - javascript for each
¿Por qué es una mala idea usar "for... in" con iteración de matriz? (25)
Dado que los elementos de JavaScript se guardan como propiedades de objeto estándar, no es recomendable iterar a través de matrices de JavaScript utilizando for ... in loop porque los elementos normales y todas las propiedades enumerables se enumerarán.
Desde https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections
Me han dicho que no use for...in
matrices en JavaScript. Por qué no?
A partir de 2016 (ES6) podremos usar for…of
para la iteración de matrices, como John Slegers ya notó.
Solo me gustaría agregar este código de demostración simple, para aclarar las cosas:
Array.prototype.foo = 1;
var arr = [];
arr[5] = "xyz";
console.log("for...of:");
var count = 0;
for (var item of arr) {
console.log(count + ":", item);
count++;
}
console.log("for...in:");
count = 0;
for (var item in arr) {
console.log(count + ":", item);
count++;
}
La consola muestra:
for...of:
0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz
for...in:
0: 5
1: foo
En otras palabras:
for...of
cuentas de 0 a 5, y también ignoraArray.prototype.foo
. Muestra valores de matriz.for...in
listas solo el5
, ignorando índices de matriz indefinidos, pero agregandofoo
. Muestra nombres de propiedades de matriz.
Además de las razones dadas en otras respuestas, es posible que no desee utilizar la estructura "para ... en" si necesita hacer cálculos matemáticos con la variable de contador porque el bucle recorre los nombres de las propiedades del objeto y, por lo tanto, la variable es una cuerda
Por ejemplo,
for (var i=0; i<a.length; i++) {
document.write(i + '', '' + typeof i + '', '' + i+1);
}
escribirá
0, number, 1
1, number, 2
...
mientras,
for (var ii in a) {
document.write(i + '', '' + typeof i + '', '' + i+1);
}
escribirá
0, string, 01
1, string, 11
...
Por supuesto, esto se puede superar fácilmente incluyendo
ii = parseInt(ii);
En el bucle, pero la primera estructura es más directa.
Además de los otros problemas, la sintaxis "for..in" es probablemente más lenta, porque el índice es una cadena, no un entero.
var a = ["a"]
for (var i in a)
alert(typeof i) // ''string''
for (var i = 0; i < a.length; i++)
alert(typeof i) // ''number''
Además, debido a la semántica, la forma for, in
arreglos de arreglos (es decir, el mismo que cualquier otro objeto de JavaScript) no está alineada con otros lenguajes populares.
// C#
char[] a = new char[] {''A'', ''B'', ''C''};
foreach (char x in a) System.Console.Write(x); //Output: "ABC"
// Java
char[] a = {''A'', ''B'', ''C''};
for (char x : a) System.out.print(x); //Output: "ABC"
// PHP
$a = array(''A'', ''B'', ''C'');
foreach ($a as $x) echo $x; //Output: "ABC"
// JavaScript
var a = [''A'', ''B'', ''C''];
for (var x in a) document.write(x); //Output: "012"
Aparte del hecho de que for
... in
bucles sobre todas las propiedades enumerables (¡que no es lo mismo que "todos los elementos de matriz"!), Consulte http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf , sección 12.6.4 (5ª edición) o 13.7.5.15 (7ª edición):
La mecánica y el orden de enumeración de las propiedades ... no se especifica ...
(Énfasis mío.)
Eso significa que si un navegador lo deseara, podría revisar las propiedades en el orden en que se insertaron. O en orden numérico. O en orden léxico (donde "30" aparece antes de "4". Tenga en cuenta que todas las claves de objeto, y por lo tanto, todos los índices de matriz, son en realidad cadenas, por lo que tiene mucho sentido). Podría pasar a través de ellos por el cubo, si implementa objetos como tablas hash. O toma algo de eso y agrega "al revés". Un navegador podría incluso iterar de forma aleatoria y ser compatible con ECMA-262, siempre y cuando visite cada propiedad exactamente una vez.
En la práctica, a la mayoría de los navegadores actualmente les gusta iterar aproximadamente en el mismo orden. Pero no hay nada que diga que tienen que hacerlo. Esa implementación es específica y podría cambiar en cualquier momento si se descubriera que otra forma es mucho más eficiente.
De cualquier manera, for
... no conlleva ninguna connotación de orden. Si le importa el orden, sea explícito y use un bucle regular for
con un índice.
Debe usar for(var x in y)
solo en listas de propiedades, no en objetos (como se explicó anteriormente).
El for
/ in
funciona con dos tipos de variables: tablas hash (matrices asociativas) y matriz (no asociativa).
JavaScript determinará automáticamente la forma en que pasa a través de los elementos. Entonces, si sabe que su matriz es realmente no asociativa, puede usar for (var i=0; i<=arrayLen; i++)
, y omita la iteración de detección automática.
Pero en mi opinión, es mejor usar for
/ in
, el proceso requerido para esa detección automática es muy pequeño.
Una respuesta real para esto dependerá de cómo el navegador analiza / interpreta el código JavaScript. Puede cambiar entre navegadores.
No puedo pensar en otros propósitos para no usar for
/ in
;
//Non-associative
var arr = [''a'', ''b'', ''c''];
for (var i in arr)
alert(arr[i]);
//Associative
var arr = {
item1 : ''a'',
item2 : ''b'',
item3 : ''c''
};
for (var i in arr)
alert(arr[i]);
El problema con for ... in ...
- y esto solo se convierte en un problema cuando un programador realmente no entiende el lenguaje; no es realmente un error ni nada, es que se repite sobre todos los miembros de un objeto (bueno, todos los miembros enumerables , pero eso es un detalle por ahora). Cuando desea iterar solo las propiedades indexadas de una matriz, la única forma garantizada de mantener las cosas semánticamente consistentes es usar un índice entero (es decir, a for (var i = 0; i < array.length; ++i)
bucle de estilo).
Cualquier objeto puede tener propiedades arbitrarias asociadas. No sería nada terrible cargar propiedades adicionales en una instancia de matriz, en particular. El código que quiere ver solo las propiedades indexadas de una matriz, por lo tanto, debe adherirse a un índice entero. El código que es completamente consciente de lo que for ... in
realmente necesita ver todas las propiedades, entonces eso también está bien.
En el aislamiento, no hay nada de malo en usar for-in en arreglos. For-in itera sobre los nombres de propiedades de un objeto, y en el caso de una matriz "fuera de la caja", las propiedades corresponden a los índices de la matriz. (Las propiedades incorporadas, como la length
, la toString
, etc., no se incluyen en la iteración).
Sin embargo, si su código (o el marco que está utilizando) agrega propiedades personalizadas a las matrices o al prototipo de matriz, estas propiedades se incluirán en la iteración, que probablemente no sea lo que desea.
Algunos frameworks JS, como Prototype, modifican el prototipo Array. Otros marcos como JQuery no lo hacen, por lo que con JQuery puede usarlo de forma segura.
Si tiene alguna duda, probablemente no debería usar for-in.
Una forma alternativa de iterar a través de una matriz es usar un bucle for:
for (var ix=0;ix<arr.length;ix++) alert(ix);
Sin embargo, esto tiene un problema diferente. El problema es que una matriz de JavaScript puede tener "agujeros". Si define arr
como:
var arr = ["hello"];
arr[100] = "goodbye";
Luego, la matriz tiene dos elementos, pero una longitud de 101. El uso de for-in producirá dos índices, mientras que el for-loop generará 101 índices, donde el 99 tiene un valor de undefined
.
Hay tres razones por las que no debería usar for..in
para iterar sobre elementos de matriz:
for..in
recorrerá todas las propiedades propias y heredadas del objeto de matriz que no sonDontEnum
; eso significa que si alguien agrega propiedades al objeto de matriz específico (hay razones válidas para esto, yo mismo lo he hecho) o cambio deArray.prototype
(que se considera una mala práctica en el código que se supone que funciona bien con otros scripts), estas propiedades se repetirán también; las propiedades heredadas se pueden excluir marcandohasOwnProperty()
, pero eso no lo ayudará con las propiedades establecidas en el objeto de matriz en sífor..in
no está garantizado para conservar el orden de los elementoses lento porque tiene que recorrer todas las propiedades del objeto de matriz y toda su cadena de prototipos y aún así solo obtendrá el nombre de la propiedad, es decir, para obtener el valor, se requerirá una búsqueda adicional
La declaración for-in
por sí misma no es una "mala práctica", sin embargo, puede ser mal utilizada , por ejemplo, para iterar sobre matrices u objetos similares a matrices.
El propósito de la declaración for-in
es enumerar sobre las propiedades del objeto. Esta declaración subirá en la cadena del prototipo, también enumerando las propiedades heredadas , algo que a veces no se desea.
Además, el orden de iteración no está garantizado por la especificación, lo que significa que si desea "iterar" un objeto de matriz, con esta declaración no puede estar seguro de que las propiedades (índices de matriz) se visitarán en el orden numérico.
Por ejemplo, en JScript (IE <= 8), el orden de enumeración incluso en los objetos de Array se define a medida que se crean las propiedades:
var array = [];
array[2] = ''c'';
array[1] = ''b'';
array[0] = ''a'';
for (var p in array) {
//... p will be "2", "1" and "0" on IE
}
Además, hablando de propiedades heredadas, si, por ejemplo, extiendes el objeto Array.prototype
(como hacen algunas bibliotecas como lo hace MooTools), esas propiedades también se enumerarán:
Array.prototype.last = function () { return this[this.length-1]; };
for (var p in []) { // an empty array
// last will be enumerated
}
Como dije antes, para iterar sobre matrices u objetos parecidos a matrices, lo mejor es usar un bucle secuencial , como un bucle de antiguo / simple para simple.
Cuando desee enumerar solo las propiedades propias de un objeto (las que no se heredan), puede usar el método hasOwnProperty
:
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
// prop is not inherited
}
}
Y algunas personas incluso recomiendan llamar al método directamente desde Object.prototype
para evitar problemas si alguien agrega una propiedad llamada hasOwnProperty
a nuestro objeto:
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// prop is not inherited
}
}
La razón es que una construcción:
var a = []; // Create a new empty array.
a[5] = 5; // Perfectly legal JavaScript that resizes the array.
for (var i = 0; i < a.length; i++) {
// Iterate over numeric indexes from 0 to 5, as everyone expects.
console.log(a[i]);
}
/* Will display:
undefined
undefined
undefined
undefined
undefined
5
*/
A veces puede ser totalmente diferente del otro:
var a = [];
a[5] = 5;
for (var x in a) {
// Shows only the explicitly set index of "5", and ignores 0-4
console.log(x);
}
/* Will display:
5
*/
También tenga en cuenta que las bibliotecas de JavaScript podrían hacer cosas como esta, que afectarán a cualquier matriz que cree:
// Somewhere deep in your JavaScript library...
Array.prototype.foo = 1;
// Now you have no idea what the below code will do.
var a = [1, 2, 3, 4, 5];
for (var x in a){
// Now foo is a part of EVERY array and
// will show up here as a value of ''x''.
console.log(x);
}
/* Will display:
0
1
2
3
4
foo
*/
No creo que tenga mucho que agregar, por ejemplo. La respuesta de Triptych o la respuesta de CMS sobre por qué debe evitarse el uso de for-in
en algunos casos.
Sin embargo, me gustaría agregar que en los navegadores modernos hay una alternativa a for-in
que puede usarse en aquellos casos for-in
que no se puede usar for-in
. Esa alternativa es for-of
for (var item of items) {
console.log(item);
}
Nota :
Desafortunadamente, ninguna versión de Internet Explorer admite esta función ( Edge 12+ ), por lo que tendrá que esperar un poco más hasta que pueda usarla en el código de producción del lado del cliente. Sin embargo, debería ser seguro utilizarlo en el código JS del lado del servidor (si utiliza Node.js ).
No es necesariamente malo (según lo que estás haciendo), pero en el caso de los arreglos, si se ha agregado algo al Array.prototype
, obtendrás resultados extraños. Donde esperas que este bucle se ejecute tres veces:
var arr = [''a'',''b'',''c''];
for (var key in arr) { ... }
Si se ha agregado una función llamada helpfulUtilityMethod
al prototype
Array
, entonces su bucle terminaría ejecutándose cuatro veces: la key
sería 0
, 1
, 2
, y helpfulUtilityMethod
. Si solo esperabas números enteros, oops.
Porque for ... in enumera a través del objeto que contiene la matriz, no la matriz en sí. Si agrego una función a la cadena de prototipos de arrays, eso también será incluido. Es decir
Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = ''foo'';
a[1] = ''bar'';
for(x in a){
document.write(x + '' = '' + a[x]);
}
Esto escribirá:
0 = foo 1 = bar myOwnFunction = function() { alert(this); }
Y como nunca puede estar seguro de que no se agregará nada a la cadena de prototipos, solo use un bucle for para enumerar la matriz:
for(i=0,x=a.length;i<x;i++){
document.write(i + '' = '' + a[i]);
}
Esto escribirá:
0 = foo 1 = bar
Porque no recorrerá las propiedades que pertenecen a los objetos en la cadena del prototipo si no tiene cuidado.
Puede usar for.. in
, solo asegúrese de verificar cada propiedad con hasOwnProperty .
Porque se enumera a través de campos de objeto, no de índices. Puedes obtener valor con el índice "longitud" y dudo que quieras esto.
Principalmente dos razones:
Uno
Como han dicho otros, es posible que obtenga claves que no están en su matriz o que se heredan del prototipo. Entonces, si, digamos, una biblioteca agrega una propiedad a los prototipos de Array u Objeto:
Array.prototype.someProperty = true
Lo obtendrás como parte de cada matriz:
for(var item in [1,2,3]){
console.log(item) // will log 1,2,3 but also "someProperty"
}
Podrías resolver esto con el método hasOwnProperty:
var ary = [1,2,3];
for(var item in ary){
if(ary.hasOwnProperty(item)){
console.log(item) // will log only 1,2,3
}
}
pero esto es cierto para iterar sobre cualquier objeto con un bucle for-in.
Dos
Por lo general, el orden de los elementos en una matriz es importante, pero el bucle for-in no necesariamente itera en el orden correcto, ya que trata la matriz como un objeto, que es la forma en que se implementa en JS, y no como una matriz. Esto parece una cosa pequeña, pero realmente puede arruinar las aplicaciones y es difícil de depurar.
Respuesta corta: Simplemente no vale la pena.
Respuesta más larga: simplemente no vale la pena, incluso si el orden de elementos secuencial y el rendimiento óptimo no son necesarios.
Respuesta larga: simplemente no vale la pena, por las siguientes razones:
- El uso de
for (var i in array) {}
hará que ''array'' se interprete como cualquier otro objeto puro , atravesando la cadena de propiedades del objeto y, en última instancia, se realice más lento que un buclefor
un índice. - No se garantiza la devolución de las propiedades del objeto en orden secuencial como se podría esperar.
- El uso de las
hasOwnProperty()
oisNaN()
para filtrar las propiedades del objeto es una sobrecarga adicional que hace que se realice (incluso más) más lento. Además, la introducción de dicha lógica adicional niega la razón clave para usarla en primer lugar, es decir, debido al formato más conciso.
Por estas razones, ni siquiera existe una compensación aceptable entre rendimiento y conveniencia. Realmente, no hay beneficio a menos que la intención sea tratar la matriz como un objeto puro y realizar operaciones en las propiedades del objeto de la matriz.
Un aspecto importante es que for...in
solo itera sobre propiedades contenidas en un objeto que tienen su atributo de propiedad enumerable establecido en verdadero. Entonces, si uno intenta iterar sobre un objeto usando for...in
entonces las propiedades arbitrarias pueden perderse si su atributo de propiedad enumerable es falso. Es bastante posible alterar el atributo de propiedad enumerable para los objetos normales de Array para que ciertos elementos no se enumeren. Aunque en general los atributos de propiedad tienden a aplicarse a las propiedades de función dentro de un objeto.
Uno puede verificar el valor del atributo de propiedad enumerable de una propiedad mediante:
myobject.propertyIsEnumerable(''myproperty'')
O para obtener los cuatro atributos de propiedad:
Object.getOwnPropertyDescriptor(myobject,''myproperty'')
Esta es una característica disponible en ECMAScript 5 - en versiones anteriores no fue posible alterar el valor del atributo de propiedad enumerable (siempre se estableció en verdadero).
Usar el bucle for...in
para una matriz no está mal, aunque puedo adivinar por qué alguien te dijo eso:
1.) Ya existe una función o método de orden superior que tiene ese propósito para una matriz, pero tiene más funcionalidad y una sintaxis más ágil, llamada ''forEach'': Array.prototype.forEach(function(element, index, array) {} );
2.) Las matrices siempre tienen una longitud, pero for...in
y forEach
no ejecutan una función para ningún valor que esté ''undefined''
, solo para los índices que tienen un valor definido. Por lo tanto, si solo asigna un valor, estos bucles solo ejecutarán una función una vez, pero como se enumera una matriz, siempre tendrá una longitud hasta el índice más alto que tenga un valor definido, pero esa longitud podría pasar desapercibida al usar estos bucles
3.) El estándar para bucle ejecutará una función tantas veces como defina en los parámetros, y dado que una matriz está numerada, tiene más sentido definir cuántas veces desea ejecutar una función. A diferencia de los otros bucles, el bucle for puede ejecutar una función para cada índice de la matriz, ya sea que el valor esté definido o no.
En esencia, puedes usar cualquier bucle, pero debes recordar exactamente cómo funcionan. Comprenda las condiciones en las que se repiten los diferentes bucles, sus funcionalidades separadas y comprenda que serán más o menos adecuadas para diferentes escenarios.
Además, puede considerarse una mejor práctica usar el forEach
método que el for...in
bucle en general, ya que es más fácil de escribir y tiene más funcionalidad, por lo que es posible que desee acostumbrarse a usar este método y este estándar, pero su llamada.
Vea a continuación que los dos primeros bucles solo ejecutan las instrucciones console.log una vez, mientras que el estándar para bucle ejecuta la función tantas veces como se especificó, en este caso, array.length = 6.
var arr = [];
arr[5] = ''F'';
for (var index in arr) {
console.log(index);
console.log(arr[index]);
console.log(arr)
}
// 5
// ''F''
// => (6) [undefined x 5, 6]
arr.forEach(function(element, index, arr) {
console.log(index);
console.log(element);
console.log(arr);
});
// 5
// ''F''
// => Array (6) [undefined x 5, 6]
for (var index = 0; index < arr.length; index++) {
console.log(index);
console.log(arr[index]);
console.log(arr);
};
// 0
// undefined
// => Array (6) [undefined x 5, 6]
// 1
// undefined
// => Array (6) [undefined x 5, 6]
// 2
// undefined
// => Array (6) [undefined x 5, 6]
// 3
// undefined
// => Array (6) [undefined x 5, 6]
// 4
// undefined
// => Array (6) [undefined x 5, 6]
// 5
// ''F''
// => Array (6) [undefined x 5, 6]
TL&DR: Usar el bucle for in
en arreglos no es malo, de hecho es todo lo contrario.
Creo que el for in
loop es una joya de JS si se usa correctamente en matrices. Se espera que usted tenga control total sobre su software y sepa lo que está haciendo. Veamos los inconvenientes mencionados y refutándolos uno por uno.
- También recorre las propiedades heredadas: en primer lugar, todas las extensiones al
Array.prototype
deberían haberse realizado mediante el uso deObject.defineProperty()
y su descriptorenumerable
debe establecerse enfalse
. Cualquier biblioteca que no lo haga no debe usarse en absoluto. - Las propiedades que se agregan a la cadena de herencia se contabilizan más adelante: cuando se realiza una
Object.setPrototypeOf
deObject.setPrototypeOf
porObject.setPrototypeOf
o por Classextend
. Debería usar nuevamenteObject.defineProperty()
que, de forma predeterminada, establece los descriptores de propiedad dewritable
,enumerable
yconfigurable
enfalse
. Veamos un ejemplo de subclasificación de matriz aquí ...
function Stack(...a){
var stack = new Array(...a);
Object.setPrototypeOf(stack, Stack.prototype);
return stack;
}
Stack.prototype = Object.create(Array.prototype); // now stack has full access to array methods.
Object.defineProperty(Stack.prototype,"constructor",{value:Stack}); // now Stack is a proper constructor
Object.defineProperty(Stack.prototype,"peak",{value: function(){ // add Stack "only" methods to the Stack.prototype.
return this[this.length-1];
}
});
var s = new Stack(1,2,3,4,1);
console.log(s.peak());
s[s.length] = 7;
console.log("length:",s.length);
s.push(42);
console.log(JSON.stringify(s));
console.log("length:",s.length);
for(var i in s) console.log(s[i]);
Así que ya ves ... for in
loop ahora es seguro ya que te preocupaste por tu código.
- El
for in
loop es lento: infierno no. Es, con mucho, el método más rápido de iteración si se repite entre matrices dispersas que se necesitan de vez en cuando. Este es uno de los trucos de rendimiento más importantes que uno debe saber. Veamos un ejemplo. Haremos un bucle sobre una matriz dispersa.
var a = [];
a[0] = "zero";
a[10000000] = "ten million";
console.time("for loop on array a:");
for(var i=0; i < a.length; i++) a[i] && console.log(a[i]);
console.timeEnd("for loop on array a:");
console.time("for in loop on array a:");
for(var i in a) a[i] && console.log(a[i]);
console.timeEnd("for in loop on array a:");
for ... in es útil cuando se trabaja en un objeto en JavaScript, pero no para un Array, pero aún no podemos decir que es una forma incorrecta, pero no se recomienda, mire este ejemplo a continuación usando for ... in loop:
let txt = "";
const person = {fname:"Alireza", lname:"Dezfoolian", age:35};
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
OK, hagámoslo con Array ahora:
let txt = "";
const person = ["Alireza", "Dezfoolian", 35];
for (const x in person) {
txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35
Como ves el resultado el mismo ...
Pero intentemos algo, prototipemos algo para Array ...
Array.prototype.someoneelse = "someoneelse";
Ahora creamos un nuevo Array ();
let txt = "";
const arr = new Array();
arr[0] = ''Alireza'';
arr[1] = ''Dezfoolian'';
arr[2] = 35;
for(x in arr) {
txt += arr[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 someoneelse
¡Ves a alguien más ! ... ¡En realidad, pasamos en bucle a través del nuevo objeto Array en este caso!
Así que esa es una de las razones por las que tenemos que usar para ... cuidadosamente, pero no siempre es el caso ...
A for ... in loop siempre enumera las claves. Las claves de propiedades de los objetos siempre son Cadena, incluso las propiedades indexadas de una matriz:
var myArray = [''a'', ''b'', ''c'', ''d''];
var total = 0
for (elem in myArray) {
total += elem
}
console.log(total); // 00123