Por que usar “for… in” com a iteração de array é uma má ideia?

Foi-me dito para não usar for...in matrizes em JavaScript. Por que não?

A razão é que uma construção:

 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 declaração do for-in por si só não é uma “má prática”, no entanto, pode ser mal utilizada , por exemplo, para iterar sobre matrizes ou objects do tipo array.

O objective da declaração de input é enumerar as propriedades do object. Essa declaração vai subir na cadeia de protótipos, também enumerando propriedades herdadas , algo que às vezes não é desejado.

Além disso, a ordem de iteração não é garantida pela especificação, o que significa que se você deseja “iterar” um object de matriz, com essa instrução você não pode ter certeza de que as propriedades (índices de matriz) serão visitadas na ordem numérica.

Por exemplo, em JScript (IE < = 8), a ordem de enumeração, mesmo em objetos Array, é definida quando as propriedades foram criadas:

 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 } 

Além disso, falando sobre propriedades herdadas, se você, por exemplo, estender o object Array.prototype (como algumas bibliotecas como o MooTools), essas propriedades também serão enumeradas:

 Array.prototype.last = function () { return this[this.length-1]; }; for (var p in []) { // an empty array // last will be enumerated } 

Como eu disse antes para iterar sobre matrizes ou objects do tipo array, o melhor é usar um loop seqüencial , como um loop for / while antigo.

Quando você deseja enumerar apenas as propriedades de um object (aquelas que não são herdadas), use o método hasOwnProperty :

 for (var prop in obj) { if (obj.hasOwnProperty(prop)) { // prop is not inherited } } 

E algumas pessoas até recomendam chamar o método diretamente do Object.prototype para evitar problemas se alguém adicionar uma propriedade chamada hasOwnProperty ao nosso object:

 for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { // prop is not inherited } } 

Existem três razões pelas quais você não deve usar for..in para iterar os elementos da matriz:

  • for..in fará um loop sobre todas as propriedades próprias e herdadas do object array que não são DontEnum ; isso significa que se alguém adicionar propriedades ao object da matriz específica (existem razões válidas para isso – eu mesmo fiz isso) ou alterei o Array.prototype (que é considerado uma prática ruim no código que deve funcionar bem com outros scripts), essas propriedades também serão iteradas; propriedades herdadas podem ser excluídas verificando hasOwnProperty() , mas isso não o ajudará com propriedades definidas no próprio object de matriz

  • for..in não é garantido para preservar a ordem dos elementos

  • é lento porque você tem que percorrer todas as propriedades do object array e toda a sua cadeia de protótipos e ainda obterá apenas o nome da propriedade, ou seja, para obter o valor, será necessária uma pesquisa adicional

Porque para … enumera através do object que contém o array, não o próprio array. Se eu adicionar uma function à cadeia de protótipos de arrays, isso também será incluído. Ou seja

 Array.prototype.myOwnFunction = function() { alert(this); } a = new Array(); a[0] = 'foo'; a[1] = 'bar'; for(x in a){ document.write(x + ' = ' + a[x]); } 

Isto irá escrever:

 0 = foo
 1 = bar
 myOwnFunction = function () {alert (isso);  }

E como você nunca pode ter certeza de que nada será adicionado à cadeia de protótipos, apenas use um loop for para enumerar a matriz:

 for(i=0,x=a.length;i 

escreverá

 0, number, 1 1, number, 2 ... 

enquanto que,

 for (var ii in a) { document.write(i + ', ' + typeof i + ', ' + i+1); } 

escreverá

 0, string, 01 1, string, 11 ... 

Claro, isso pode ser facilmente superado incluindo

 ii = parseInt(ii); 

no loop, mas a primeira estrutura é mais direta.

A partir de 2016 (ES6) podemos usar for…of iteração de array, como John Slegers já notou.

Eu gostaria apenas de adicionar este simples código de demonstração para tornar as coisas mais claras:

 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++; } 

O console mostra:

 for...of: 0: undefined 1: undefined 2: undefined 3: undefined 4: undefined 5: xyz for...in: 0: 5 1: foo 

Em outras palavras:

  • for...of contagens de 0 a 5 e também ignora Array.prototype.foo . Mostra os valores da matriz.

  • for...in listas apenas os 5 , ignorando índices de matriz indefinidos, mas adicionando foo . Mostra os nomes das propriedades da matriz.

Além do fato de que forin loops sobre todas as propriedades enumeráveis ​​(que não é o mesmo que “all array elements”!), Veja http://www.ecma-international.org/publications/files/ECMA-ST /Ecma-262.pdf , seção 12.6.4 (5ª edição) ou 13.7.5.15 (7ª edição):

A mecânica e ordem de enumerar as propriedades … não é especificada

(Ênfase minha)

Isso significa que, se um navegador quisesse, ele poderia passar pelas propriedades na ordem em que elas foram inseridas. Ou em ordem numérica. Ou em ordem lexical (onde “30” vem antes de “4”! Tenha em mente que todas as chaves de objects – e, portanto, todos os índices de array – são na verdade strings, então isso faz sentido). Poderia passar por eles por balde, se implementasse objects como tabelas hash. Ou pegue tudo isso e adicione “para trás”. Um navegador pode até mesmo iterar aleatoriamente e ser compatível com ECMA-262, desde que tenha visitado cada propriedade exatamente uma vez.

Na prática, a maioria dos navegadores prefere iterar aproximadamente na mesma ordem. Mas não há nada dizendo que eles precisam. Essa implementação é específica e poderia mudar a qualquer momento se outra forma fosse considerada muito mais eficiente.

De qualquer maneira, for … não tem conotação de ordem. Se você se preocupa com a ordem, seja explícito sobre ela e use um loop regular for um índice.

Resposta curta: Não vale a pena.


Resposta mais longa: Não vale a pena, mesmo que a ordem dos elementos sequenciais e o desempenho ideal não sejam necessários.


Resposta longa: Não vale a pena, pelas seguintes razões:

  • O uso for (var i in array) {} fará com que o ‘array’ seja interpretado como qualquer outro object puro , atravessando a cadeia de propriedades do object e, por fim, com desempenho mais lento que um loop baseado for índice.
  • Não é garantido retornar as propriedades do object em ordem seqüencial como se poderia esperar.
  • Usar as verificações hasOwnProperty() ou isNaN() para filtrar as propriedades do object é uma sobrecarga adicional, fazendo com que ele seja executado (ainda mais) mais lentamente. Além disso, introduzir tal lógica adicional nega a principal razão para usá-lo em primeiro lugar, ou seja, por causa do formato mais conciso.

Por essas razões, um compromisso aceitável entre desempenho e conveniência não existe. Realmente, não há nenhum benefício a menos que a intenção seja tratar a matriz como um object puro e executar operações nas propriedades do object da matriz.

Principalmente duas razões:

1

Como outros disseram, você pode obter chaves que não estão em sua matriz ou que são herdadas do protótipo. Portanto, se, digamos, uma biblioteca adicionar uma propriedade aos protótipos da Matriz ou do Objeto:

 Array.prototype.someProperty = true 

Você terá isso como parte de cada array:

 for(var item in [1,2,3]){ console.log(item) // will log 1,2,3 but also "someProperty" } 

você poderia resolver isso com o 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 } } 

mas isso é verdade para iterar sobre qualquer object com um loop for-in.

Dois

Normalmente, a ordem dos itens em uma matriz é importante, mas o loop for-in não necessariamente itera na ordem correta, porque trata a matriz como um object, que é a maneira como ela é implementada no JS, e não como uma matriz. Isso parece uma coisa pequena, mas pode realmente estragar aplicativos e é difícil de depurar.

Porque enumera através de campos de objects, não de índices. Você pode obter valor com o índice “comprimento” e duvido que você quer isso.

O problema com for ... in ... – e isso só se torna um problema quando um programador realmente não entende a linguagem; não é realmente um bug ou qualquer coisa – é que itera sobre todos os membros de um object (bem, todos os membros enumeráveis , mas isso é um detalhe por agora). Quando você deseja iterar apenas as propriedades indexadas de uma matriz, a única maneira garantida de manter as coisas semanticamente consistentes é usar um índice inteiro (ou seja, a for (var i = 0; i < array.length; ++i) loop de estilo).

Qualquer object pode ter propriedades arbitrárias associadas a ele. Não haveria nada de terrível em carregar propriedades adicionais em uma instância de matriz, em particular. O código que deseja ver apenas propriedades indexadas como matrizes, portanto, deve se ater a um índice inteiro. Código que está plenamente ciente do que for ... in e realmente precisa ver todas as propriedades, bem, então tudo bem também.

Além disso, devido à semântica, o caminho for, in matrizes de tratamento (ou seja, o mesmo que qualquer outro object JavaScript) não está alinhado com outras linguagens 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" 

Eu não acho que tenho muito a acrescentar, por exemplo. A resposta do Triptych ou a resposta do CMS sobre o porquê de usar o for-in deve ser evitada em alguns casos.

Eu gostaria, no entanto, de acrescentar que nos navegadores modernos existe uma alternativa ao for-in que pode ser usada nos casos em que o for-in não pode ser usado. Essa alternativa é for-of :

 for (var item of items) { console.log(item); } 

Nota :

Infelizmente, nenhuma versão do Internet Explorer suporta esse recurso ( Edge 12+ ), então você terá que esperar um pouco mais até que você possa usá-lo no código de produção do lado do cliente. No entanto, deve ser seguro usar o código JS do lado do servidor (se você usar o Node.js ).

Além dos outros problemas, a syntax “for..in” provavelmente é mais lenta, porque o índice é uma string, não um inteiro.

 var a = ["a"] for (var i in a) alert(typeof i) // 'string' for (var i = 0; i < a.length; i++) alert(typeof i) // 'number' 

Um aspecto importante é que for...in apenas itera sobre propriedades contidas em um object que tem seu atributo de propriedade enumerável definido como true. Portanto, se alguém tentar iterar sobre um object usando for...in então, propriedades arbitrárias podem ser perdidas se seu atributo de propriedade enumerável for falso. É bem possível alterar o atributo de propriedade enumerável para objects Array normais para que determinados elementos não sejam enumerados. Embora, em geral, os atributos da propriedade tendem a se aplicar às propriedades da function dentro de um object.

Pode-se verificar o valor do atributo de propriedade enumerável de uma propriedade por:

 myobject.propertyIsEnumerable('myproperty') 

Ou para obter todos os quatro atributos de propriedade:

 Object.getOwnPropertyDescriptor(myobject,'myproperty') 

Este é um recurso disponível no ECMAScript 5 – em versões anteriores, não era possível alterar o valor do atributo de propriedade enumerável (sempre era definido como true).

O for / in trabalha com dois tipos de variables: hashtables (matrizes associativas) e array (não-associativas).

O JavaScript determinará automaticamente a maneira como ele passa pelos itens. Então, se você sabe que seu array é realmente não-associativo, você pode usar for (var i=0; i< =arrayLen; i++) e ignorar a iteração de detecção automática.

Mas na minha opinião, é melhor usar for / in , o processo necessário para que a detecção automática seja muito pequena.

Uma resposta real para isso dependerá de como o navegador analisa / interpreta o código JavaScript. Pode mudar entre navegadores.

Não consigo pensar em outros propósitos para não 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]); 

TL & DR: Usar o loop for in em arrays não é mal, na verdade é exatamente o oposto.

Eu acho que o for in loop é uma jóia do JS se usado corretamente em matrizes. Espera-se que você tenha controle total sobre seu software e saiba o que está fazendo. Vamos ver os inconvenientes mencionados e refutá-los um por um.

  1. Ele também passa por propriedades herdadas: Primeiro, todas as extensões para o Array.prototype devem ter sido feitas usando Object.defineProperty() e seu descritor enumerable deve ser definido como false . Qualquer biblioteca que não esteja fazendo isso não deve ser usada.
  2. As propriedades que você adiciona à cadeia de inheritance mais tarde são contadas: Ao fazer a subsorting de array por Object.setPrototypeOf ou por Class extend . Você deve novamente usar Object.defineProperty() que, por padrão, define os descritores de propriedade writable , enumerable e configurable como false . Vamos ver um exemplo de subsorting de array aqui …
 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]); 

Porque iterará sobre propriedades pertencentes a objects na cadeia de protótipos se você não for cuidadoso.

Você pode usar for.. in , apenas certifique-se de verificar cada propriedade com hasOwnProperty .

Não é necessariamente ruim (com base no que você está fazendo), mas no caso de arrays, se algo foi adicionado ao Array.prototype , então você obterá resultados estranhos. Onde você esperaria que esse loop fosse executado três vezes:

 var arr = ['a','b','c']; for (var key in arr) { ... } 

Se uma function chamada helpfulUtilityMethod tiver sido adicionada ao prototype Array , seu loop acabará sendo executado quatro vezes: a key seria 0 , 1 , 2 e helpfulUtilityMethod . Se você estivesse apenas esperando inteiros, oops.

Você deve usar o for(var x in y) somente nas listas de propriedades, não nos objects (como explicado acima).

Usar o loop for...in para um array não está errado, embora eu possa imaginar por que alguém lhe disse isso:

1.) Já existe uma function de ordem superior, ou método, que tem esse propósito para uma matriz, mas tem mais funcionalidade e syntax mais enxuta, chamada ‘forEach’: Array.prototype.forEach(function(element, index, array) {} );

2.) Arrays sempre tem um comprimento, mas for...in e forEach não executam uma function para qualquer valor que seja 'undefined' , apenas para os índices que possuem um valor definido. Portanto, se você atribuir apenas um valor, esses loops só executarão uma function uma vez, mas como uma matriz é enumerada, ela sempre terá um tamanho até o índice mais alto que tenha um valor definido, mas esse comprimento pode passar despercebido ao usá-los rotações.

3.) O padrão for loop executará uma function quantas vezes você definir nos parâmetros, e como uma matriz é numerada, faz mais sentido definir quantas vezes você deseja executar uma function. Ao contrário dos outros loops, o loop for pode executar uma function para cada índice na matriz, independentemente de o valor estar definido ou não.

Em essência, você pode usar qualquer loop, mas deve se lembrar exatamente como eles funcionam. Entenda as condições sob as quais os diferentes loops reiteram, suas funcionalidades separadas e perceba que eles serão mais ou menos apropriados para cenários diferentes.

Além disso, pode ser considerado uma prática melhor usar o método forEach que o loop for...in em geral, porque é mais fácil de escrever e tem mais funcionalidade, portanto você pode querer adquirir o hábito de usar este método apenas e padrão para, mas sua chamada.

Veja abaixo que os dois primeiros loops executam apenas as instruções console.log uma vez, enquanto o padrão for loop executa a function quantas vezes forem especificadas, neste 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] 

for…in is useful when working on an object in JavaScript, but not for an Array, but still we can not say it’s a wrong way, but it’s not recommended, look at this example below using 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, let’s do it with Array now:

 let txt = ""; const person = ["Alireza", "Dezfoolian", 35]; for (const x in person) { txt += person[x] + " "; } console.log(txt); //Alireza Dezfoolian 35 

As you see the result the same…

But let’s try something, let’s prototype something to Array

 Array.prototype.someoneelse = "someoneelse"; 

Now we create a new 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 

You see the someoneelse !!!… We actually looping through new Array object in this case!

So that’s one of the reasons why we need to use for..in carefully, but it’s not always the case…

Since JavaScript elements are saved as standard object properties, it is not advisable to iterate through JavaScript arrays using for…in loops because normal elements and all enumerable properties will be listed.

From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections

A for…in loop always enumerates the keys. Objects properties keys are always String, even the indexed properties of an array :

 var myArray = ['a', 'b', 'c', 'd']; var total = 0 for (elem in myArray) { total += elem } console.log(total); // 00123