TL; DR
Mas há muito mais para explorar, continue a ler ...
O JavaScript possui uma semântica poderosa para fazer loop através de matrizes e objetos semelhantes a matrizes. Dividi a resposta em duas partes: opções para matrizes genuínas e opções para coisas parecidas com matrizes , como o argumentsobjeto, outros objetos iteráveis (ES2015 +), coleções DOM e assim por diante.
Observarei rapidamente que você pode usar as opções do ES2015 agora , mesmo nos mecanismos ES5, transpilando o ES2015 para o ES5. Pesquise "transpiling ES2015" / "transpiling ES6" para obter mais ...
Ok, vejamos nossas opções:
Para matrizes reais
Você tem três opções no ECMAScript 5 ("ES5"), a versão mais amplamente suportada no momento, e mais duas adicionadas no ECMAScript 2015 ("ES2015", "ES6"):
- Uso
forEache afins (ES5 +)
- Use um
forloop simples
- Use
for-in corretamente
- Use
for-of(use um iterador implicitamente) (ES2015 +)
- Use um iterador explicitamente (ES2015 +)
Detalhes:
1. Uso forEache afins
Em qualquer ambiente vagamente moderno (portanto, não no IE8) em que você tenha acesso aos Arrayrecursos adicionados pelo ES5 (diretamente ou usando polyfills), você pode usar forEach( spec| MDN):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEachaceita uma função de retorno de chamada e, opcionalmente, um valor para usar como thisao chamar esse retorno de chamada (não usado acima). O retorno de chamada é chamado para cada entrada na matriz, em ordem, ignorando entradas inexistentes em matrizes esparsas. Embora eu tenha usado apenas um argumento acima, o retorno de chamada é chamado com três: O valor de cada entrada, o índice dessa entrada e uma referência à matriz na qual você está iterando (caso sua função ainda não a tenha útil) )
A menos que você ofereça suporte a navegadores obsoletos como o IE8 (que o NetApps mostra com pouco mais de 4% de participação de mercado até o momento em que escrevemos em setembro de 2016), você pode usar felizmente forEachuma página da Web de uso geral sem nenhum calço. Se você precisar oferecer suporte a navegadores obsoletos, forEaché fácil fazer o shimming / polyfilling (pesquise "es5 shim" para várias opções).
forEach tem o benefício de que você não precisa declarar variáveis de indexação e valor no escopo que contém, pois elas são fornecidas como argumentos para a função de iteração e têm um escopo adequado para essa iteração.
Se você está preocupado com o custo de tempo de execução de uma chamada de função para cada entrada da matriz, não fique; detalhes .
Além disso, forEaché a função "loop through all all", mas o ES5 definiu várias outras funções úteis "percorrer a matriz e executar tarefas", incluindo:
every(para de repetir a primeira vez que o retorno de chamada retorna falseou algo está errado)
some(para de repetir a primeira vez que o retorno de chamada trueou algo verdadeiro)
filter(cria uma nova matriz, incluindo elementos onde a função de filtro retorna truee omite os que retornam false)
map (cria uma nova matriz a partir dos valores retornados pelo retorno de chamada)
reduce (cria um valor chamando repetidamente o retorno de chamada, transmitindo valores anteriores; consulte a especificação para obter detalhes; útil para resumir o conteúdo de uma matriz e muitas outras coisas)
reduceRight(como reduce, mas funciona em ordem decrescente em vez de crescente)
2. Use um forloop simples
Às vezes, os modos antigos são os melhores:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Se o comprimento da matriz não vai mudar durante o ciclo, e é em código sensível ao desempenho (improvável), uma versão um pouco mais complicado agarrando o comprimento na frente pode ser um minúsculo pouco mais rápido:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
E / ou contando para trás:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Mas, com os modernos mecanismos JavaScript, é raro você precisar aproveitar esse último pedaço de suco.
No ES2015 e superior, você pode tornar suas variáveis de índice e valor locais para o forloop:
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
//console.log(index); // would cause "ReferenceError: index is not defined"
//console.log(value); // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
try {
console.log(index);
} catch (e) {
console.error(e); // "ReferenceError: index is not defined"
}
try {
console.log(value);
} catch (e) {
console.error(e); // "ReferenceError: value is not defined"
}
E quando você faz isso, não só valuemas também indexé recriado para cada iteração do loop, ou seja, fechamentos criados no corpo do loop manter uma referência ao index(e value) criado para a iteração específica:
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
Se você tivesse cinco divs, obteria "O índice é: 0" se você clicou no primeiro e "O índice é: 4" se você clicou no último. Isso não funciona se você usar em varvez de let.
3. Use for-in corretamente
Você fará as pessoas dizerem para você usar for-in, mas não for-iné para isso . for-inpercorre as propriedades enumeráveis de um objeto , não os índices de uma matriz. O pedido não é garantido , nem mesmo no ES2015 (ES6). ES2015 + faz definir uma ordem para as propriedades do objeto (via [[OwnPropertyKeys]], [[Enumerate]]e as coisas que os utilizam como Object.getOwnPropertyKeys), mas não define que for-iniria seguir essa ordem; ES2020 fez, no entanto. (Detalhes nesta outra resposta .)
Os únicos casos de uso reais for-inem uma matriz são:
- É uma matriz esparsa com lacunas enormes , ou
- Você está usando propriedades que não são elementos e deseja incluí-las no loop
Observando apenas o primeiro exemplo: você pode for-invisitar esses elementos esparsos da matriz se usar as salvaguardas apropriadas:
// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
if (a.hasOwnProperty(key) && // These checks are
/^0$|^[1-9]\d*$/.test(key) && // explained
key <= 4294967294 // below
) {
console.log(a[key]);
}
}
Observe as três verificações:
Que o objeto tem sua própria propriedade com esse nome (não herdado de seu protótipo) e
Que a chave tem todos os dígitos decimais (por exemplo, forma normal de string, não notação científica) e
Que o valor da chave quando coagido a um número é <= 2 ^ 32 - 2 (que é 4.294.967.294). De onde vem esse número? Faz parte da definição de um índice de matriz na especificação . Outros números (não inteiros, números negativos, números maiores que 2 ^ 32 - 2) não são índices de matriz. A razão é 2 ^ 32 - 2 é que faz com que o maior valor de índice um menor que 2 ^ 32 - 1 , que é o valor máximo de uma matriz lengthpode ter. (Por exemplo, o comprimento de uma matriz se encaixa em um número inteiro não assinado de 32 bits.) (Adota o RobG por apontar em um comentário no meu blog que o meu teste anterior não estava certo.)
Você não faria isso em código embutido, é claro. Você escreveria uma função utilitária. Possivelmente:
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
4. Use for-of(use um iterador implicitamente) (ES2015 +)
O ES2015 adicionou iteradores ao JavaScript. A maneira mais fácil de usar iteradores é a nova for-ofdeclaração. Se parece com isso:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Sob as cobertas, ele obtém um iterador da matriz e passa por ela, obtendo os valores dela. Isso não tem o problema que usar for-inhas, porque usa um iterador definido pelo objeto (a matriz), e as matrizes definem que seus iteradores iteram pelas entradas (não pelas propriedades). Diferentemente for-indo ES5, a ordem na qual as entradas são visitadas é a ordem numérica de seus índices.
5. Use um iterador explicitamente (ES2015 +)
Às vezes, convém usar um iterador explicitamente . Você pode fazer isso também, embora seja muito mais complicado do que isso for-of. Se parece com isso:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
O iterador é um objeto que corresponde à definição do iterador na especificação. Seu nextmétodo retorna um novo objeto de resultado toda vez que você o chama. O objeto de resultado possui uma propriedade, doneinformando se foi concluída, e uma propriedade valuecom o valor dessa iteração. ( doneé opcional se for false, valueé opcional se for undefined.)
O significado de valuevaria dependendo do iterador; matrizes suportam (pelo menos) três funções que retornam iteradores:
values(): Este é o que eu usei acima. Devolve uma iteração, onde cada um valueé a entrada da matriz para a iteração ( "a", "b", e "c"no exemplo anterior).
keys(): Retorna um iterador onde cada valueé a chave para a iteração (assim para o nosso aacima, que seria "0", então "1", a seguir "2").
entries(): Retorna um iterador em que cada valueum é uma matriz no formulário [key, value]para essa iteração.
Para objetos semelhantes a matrizes
Além de matrizes verdadeiras, também existem objetos semelhantes a matrizes que possuem uma lengthpropriedade e propriedades com nomes numéricos: NodeListinstâncias, argumentsobjeto, etc. Como percorrer o conteúdo deles?
Use qualquer uma das opções acima para matrizes
Pelo menos algumas das abordagens de matriz acima, e possivelmente a maioria ou mesmo todas, se aplicam com freqüência igualmente bem a objetos do tipo matriz:
Uso forEache afins (ES5 +)
As várias funções ativadas Array.prototypesão "intencionalmente genéricas" e geralmente podem ser usadas em objetos do tipo array via Function#callou Function#apply. (Consulte a Advertência para obter os objetos fornecidos pelo host no final desta resposta, mas é um problema raro.)
Suponha que você queira usar forEachem um Node's childNodespropriedade. Você faria isso:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Se você fizer muito isso, convém pegar uma cópia da referência de função em uma variável para reutilização, por exemplo:
// (This is all presumably in some scoping function)
var forEach = Array.prototype.forEach;
// Then later...
forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Use um forloop simples
Obviamente, um forloop simples se aplica a objetos do tipo array.
Use for-in corretamente
for-incom as mesmas salvaguardas que uma matriz, também deve funcionar com objetos semelhantes a matrizes; a ressalva para os objetos fornecidos pelo host em # 1 acima pode ser aplicada.
Use for-of(use um iterador implicitamente) (ES2015 +)
for-ofusa o iterador fornecido pelo objeto (se houver). Isso inclui objetos fornecidos pelo host. Por exemplo, a especificação para NodeListfrom querySelectorAllfoi atualizada para suportar a iteração. A especificação para o HTMLCollectionde getElementsByTagNamenão era.
Use um iterador explicitamente (ES2015 +)
Veja # 4.
Crie uma matriz verdadeira
Outras vezes, convém converter um objeto semelhante a uma matriz em uma matriz verdadeira. Fazer isso é surpreendentemente fácil:
Use o slicemétodo de matrizes
Podemos usar o slicemétodo de matrizes, que, como os outros métodos mencionados acima, é "intencionalmente genérico" e, portanto, pode ser usado com objetos semelhantes a matrizes, assim:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Por exemplo, se quisermos converter um NodeListem um array verdadeiro, poderíamos fazer isso:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Veja a Advertência para obter os objetos fornecidos pelo host abaixo. Em particular, observe que isso falhará no IE8 e versões anteriores, o que não permite que você use objetos fornecidos pelo host thisdessa maneira.
Usar sintaxe de propagação ( ...)
Também é possível usar a sintaxe de propagação do ES2015 com mecanismos JavaScript compatíveis com esse recurso. Assim for-of, isso usa o iterador fornecido pelo objeto (consulte o item 4 na seção anterior):
var trueArray = [...iterableObject];
Por exemplo, se queremos converter um NodeListem um array verdadeiro, com a sintaxe espalhada, isso se torna bastante sucinto:
var divs = [...document.querySelectorAll("div")];
Usar Array.from
Array.from (especificação) | (MDN) (ES2015 +, mas facilmente preenchido com polifólio) cria uma matriz a partir de um objeto semelhante a uma matriz, passando opcionalmente as entradas por meio de uma função de mapeamento. Assim:
var divs = Array.from(document.querySelectorAll("div"));
Ou, se você deseja obter uma matriz dos nomes de tags dos elementos com uma determinada classe, use a função de mapeamento:
// Arrow function (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standard function (since `Array.from` can be shimmed):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Advertência para objetos fornecidos pelo host
Se você usar Array.prototypefunções com objetos do tipo matriz fornecidos pelo host (listas DOM e outras coisas fornecidas pelo navegador em vez do mecanismo JavaScript), será necessário testar nos ambientes de destino para garantir que o objeto fornecido pelo host se comporte corretamente . A maioria se comporta corretamente (agora), mas é importante testar. O motivo é que a maioria dos Array.prototypemétodos que você provavelmente deseja usar depende do objeto fornecido pelo host, fornecendo uma resposta honesta à [[HasProperty]]operação abstrata . No momento da redação deste artigo, os navegadores fazem um bom trabalho, mas a especificação 5.1 permitiu a possibilidade de um objeto fornecido pelo host não ser honesto. Está no §8.6.2 , vários parágrafos abaixo da tabela grande perto do início dessa seção), onde diz:
Os objetos host podem implementar esses métodos internos de qualquer maneira, a menos que especificado de outra forma; por exemplo, uma possibilidade é que [[Get]]e [[Put]]para um objeto de host específico, de fato buscar e armazenar valores de propriedade, mas [[HasProperty]]sempre gera falsa .
(Eu não poderia encontrar o palavreado equivalente no ES2015 spec, mas é obrigado a continuar a ser o caso.) Mais uma vez, como esta escrito o comum matriz como objetos em navegadores modernos [fornecido pelo anfitrião NodeListcasos, por exemplo] fazer alça [[HasProperty]]corretamente, mas é importante testar.)
forEache não apenasfor. como afirmado, em c # era um pouco diferente, e isso me confundiu :) #