Iteração em JavaScript bem feita

Existem 4 instruções de iteração em JavaScript: for, for-in, while e do-while. Aqui estão algumas dicas sobre como e quando usá-los para obter o máximo de legibilidade e desempenho, e também algumas dicas que você deve levar em consideração.

For-in

Se você é novo em JavaScript, cuidado: infelizmente não é a construção for-each útil que você provavelmente usou em outras linguagens (embora for-of seja e esteja no caminho, mas isso é outro tópico).

JavaScript for-in itera sobre as chaves de propriedade enumeráveis ​​de um objeto, as próprias e aquelas em sua cadeia de protótipo. Você provavelmente já leu que não deve usá-lo com matrizes, mas nem sempre por quê. Essas são as pegadinhas com arrays e loop for-in:

  • For-in itera sobre chaves de propriedade de objeto, e Arrays são apenas objetos com chaves inteiras e alguns métodos úteis. Eles podem ter outras propriedades enumeráveis ​​ou herdá-las de sua cadeia de protótipo:
var arr = ['a', 'b', 'c'];
arr
.foo = 'bar';

for (var key in arr) {
// Logs 0-a, 1-b, 2-c, foo-bar.
console
.log(key + '-' + arr[key]);
}
  • For-int não garante nenhuma ordem de iteração, pelo menos em uma configuração de navegador cruzado.

  • For-in não itera sobre o comprimento. As matrizes de JavaScript podem ser não consecutivas (ou esparsas), embora não devam. É, na minha opinião, uma falha de design. Não use matrizes dessa maneira. Se você precisar de índices esparsos, use objetos.

var arr = ['a', 'b', 'c'];
arr
[100] = 'foo';

// It logs 101.
console
.log(arr.length);

for (var key in arr) {
// It logs 0, 1, 2 and 101.
console
.log(key);
}
  • Mesmo que você não esteja preocupado com os motivos acima, saiba que o for-in é terrivelmente lento . Eu esperaria que fosse duas ou três vezes mais lento, mas é pior: parece que usá-lo impede as otimizações JIT do compilador e você poderia facilmente terminar com algo 100 vezes mais lento.

Isso significa que for-in nunca é útil? De modo nenhum! Use-o para iterar por meio das chaves de propriedade enumeráveis ​​de um objeto. Filtre com hasOwnProperty se você não quiser propriedades em sua cadeia de protótipo.

var obj = { foo: 'bar', baz: 'qux' };

for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// Logs foo, baz.
console
.log(key);
}
}

No ES5, você também tem Object.keys (obj), que retorna uma matriz com as próprias chaves de propriedade enumeráveis ​​do objeto.

var obj = { foo: 'bar', baz: 'qux' };
Object.keys(obj); // Returns ['foo', 'baz']

Outra função útil é Object.getOwnPropertyNames (), que retorna todas as propriedades dos próprios objetos (também as não enumeráveis). Com eles, você não deve usar for-in com muita frequência.

Por (e enquanto)

O bom e velho loop for é mais intuitivo do que o for-in, o que é uma boa notícia para facilitar a leitura e manutenção. É muito rápido também, mesmo se você não armazenar em cache arr.length. Não se preocupe; essas micro-otimizações são um trabalho fácil para os compiladores JavaScript JIT de hoje e não melhoram o desempenho de forma alguma.

for (var i = 0; i < arr.length; i++) {
}

No entanto, às vezes podemos fazer melhor. Se você estiver iterando um array temporário e não se importar em remover os elementos, eles são realmente elegantes e legíveis:

var elem;

// Straight loop.
while(elem = arr.shift()) {
// Code
}

// Backwards loop
while(elem = arr.pop()) {
// Code
}

Não são ótimos? Eles são até ordens de magnitude mais rápidos do que os bons e velhos! Mas espere, também existem algumas pegadinhas não tão óbvias com isso. Se a matriz contiver qualquer valor falsey, por exemplo, um zero ou uma string vazia, ele irá parar aí e não irá iterar a partir de então. Poderíamos consertar dessa forma, pois sabemos que shift () e pop () em um array vazio retornam indefinido:

// Backwards loop
while((elem=arr.pop()) !== undefined) {
// Code
}

Mas não é mais tão legível e não funciona para matrizes esparsas. De jeito nenhum.

Esta é a maneira mais legível e eficiente de percorrer um array:

var arr = ['a', 'b', 'c'];

// If you don't mind removing elements
while(arr.length) {
var elem = arr.pop(); // Or shift().
}

// If you don't want to remove element's
// and don't mind backwards looping.
var len = arr.length;
while(len--) {
var elem = arr[len];
}

Para loops mais complexos, use good ol ‘for, e se você tiver que iterar 1 para n em vez de 0 para n, dê uma chance ao do-while.

Há também Array.forEach (), o que é bom, mas é inerentemente mais lento e precisa de uma função. Freqüentemente, isso envolve a criação de um encerramento referenciando variáveis ​​de que você não precisa, o que é um exagero.

Verifique estes testes para comparações de desempenho entre diferentes tipos de loop.

Respostas relacionadas:

javascript foreach