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 arguments
objeto, 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
forEach
e afins (ES5 +)
- Use um
for
loop simples
- Use
for-in
corretamente
- Use
for-of
(use um iterador implicitamente) (ES2015 +)
- Use um iterador explicitamente (ES2015 +)
Detalhes:
1. Uso forEach
e afins
Em qualquer ambiente vagamente moderno (portanto, não no IE8) em que você tenha acesso aos Array
recursos 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);
});
forEach
aceita uma função de retorno de chamada e, opcionalmente, um valor para usar como this
ao 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 forEach
uma 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 false
ou algo está errado)
some
(para de repetir a primeira vez que o retorno de chamada true
ou algo verdadeiro)
filter
(cria uma nova matriz, incluindo elementos onde a função de filtro retorna true
e 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 for
loop 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 for
loop:
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ó value
mas 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 var
vez 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-in
percorre 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-in
iria seguir essa ordem; ES2020 fez, no entanto. (Detalhes nesta outra resposta .)
Os únicos casos de uso reais for-in
em 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-in
visitar 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 length
pode 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-of
declaraçã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-in
has, 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-in
do 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 next
método retorna um novo objeto de resultado toda vez que você o chama. O objeto de resultado possui uma propriedade, done
informando se foi concluída, e uma propriedade value
com o valor dessa iteração. ( done
é opcional se for false
, value
é opcional se for undefined
.)
O significado de value
varia 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 a
acima, que seria "0"
, então "1"
, a seguir "2"
).
entries()
: Retorna um iterador em que cada value
um é 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 length
propriedade e propriedades com nomes numéricos: NodeList
instâncias, arguments
objeto, 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 forEach
e afins (ES5 +)
As várias funções ativadas Array.prototype
são "intencionalmente genéricas" e geralmente podem ser usadas em objetos do tipo array via Function#call
ou 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 forEach
em um Node
's childNodes
propriedade. 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 for
loop simples
Obviamente, um for
loop simples se aplica a objetos do tipo array.
Use for-in
corretamente
for-in
com 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-of
usa o iterador fornecido pelo objeto (se houver). Isso inclui objetos fornecidos pelo host. Por exemplo, a especificação para NodeList
from querySelectorAll
foi atualizada para suportar a iteração. A especificação para o HTMLCollection
de getElementsByTagName
nã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 slice
método de matrizes
Podemos usar o slice
mé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 NodeList
em 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 this
dessa 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 NodeList
em 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.prototype
funçõ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.prototype
mé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 NodeList
casos, por exemplo] fazer alça [[HasProperty]]
corretamente, mas é importante testar.)
forEach
e não apenasfor
. como afirmado, em c # era um pouco diferente, e isso me confundiu :) #