Dia 6 — Laços de repetição
for e forEach
for e forEach
Repetição é o que o laço faz: um laço repete um bloco de código. O for sem chaves sofre do mesmo defeito do if sem chaves: o for é o laço com
contador, e o bloco do laço é o que repete. Três partes separadas por ponto e vírgula — a variável do laço com o valor
inicial, a condição que precisa continuar verdadeira, e o passo.
for (let i = 0; i < 3; i++) { console.log(i); }
Cada iteração é uma volta. O JavaScript confere a condição antes de cada
volta, e sai quando ela falha — o i < 3 produz 0, 1, 2, e no i igual a
3 o laço termina. É por isso que o < fica e não o <=: com <= seriam quatro
voltas.
O passo não precisa ser i++. i += 2 pula de dois em dois, e i-- desce:
for (let i = 2; i <= 8; i += 2) { console.log(i); } // 2, 4, 6, 8
O exemplo imprime as três voltas (i = 0, i = 1, i = 2) e logo depois tenta
usar o i fora do laço, num try/catch que devolve
depois do laco: ReferenceError - i is not defined. Não é coincidência: o let
do for cria um nome que pertence só ao laço, e por isso ele desaparece no fim.
Um var no lugar do let sobreviveria, que é o vazamento de escopo da aula de
let e const aparecendo aqui de novo.
O lado do <= está duas linhas abaixo, e o console imprime volta 0, volta 1
e volta 2 — três linhas, e não quatro. A conta é a mesma: 0 <= 2, 1 <= 2 e
2 <= 2 passam, e 3 <= 2 falha.
O passo aparece em duas sequências: de dois em dois: 2, 4, 6, 8 — quatro
voltas porque o <= aqui é o do exemplo, e descendo: 3, 2, 1 — três voltas
com i--, encerrando em 1 porque a condição i > 0 falha nele.
forEach: a volta que já sabe o tamanho
forEach é o método do array que executa uma função uma vez para cada item. Não
tem contador, não tem condição e não tem passo — o array é o que controla o
número de voltas:
const tarefas = ['estudar', 'treinar']; tarefas.forEach((tarefa, indice) => { console.log(indice, tarefa); });
A função passada para o forEach recebe três argumentos: o item, o índice
e o array inteiro. Cada volta cria um escopo próprio, então declarar const dentro
do laço é seguro e o nome não vaza para fora.
forEach não devolve nada. É o detalhe que faz ele ser preterido em favor do
map quando o resultado importa: o forEach serve para efeito colateral —
registrar, contar, chamar função — e o map serve para produzir um array novo.
O exemplo passa a lista de três tarefas pelo forEach e imprime três linhas com
o índice antes do texto: forEach: 0 estudar, forEach: 1 treinar, `forEach: 2
descansar. Três voltas porque o array tem três itens — o forEach` não tem
condição para consultar, e é isso que o difere do for.
A linha do retorno é a prova de que ele não devolve nada:
retorno do forEach: undefined. A função passada devolvia t.length, o tamanho
cada texto, e o resultado da chamada inteira foi undefined. Com o map, o
mesmo t.length teria produzido [7, 7, 9] — um array com o tamanho de cada
item. A escolha entre os dois é sempre a mesma: se o resultado volta para a
tela, é map; se só o registro importa, é forEach.
E o exemplo mostra o uso que o material chama de efeito colateral:
tarefas.forEach(() => { total = total + 1; }) termina em
quantas tarefas: 3. A seta não devolve nada e ninguém espera que devolva; o que
ela faz é mexer no total de fora, uma volta por item. É o forEach na função
de contar, que é metade dos casos em que ele é a escolha certa.
break e continue dentro do laço
break sai do laço inteiro. continue pula para a próxima volta, deixando o resto
do bloco de fora:
for (const n of [1, 2, 3, 4, 5]) { if (n === 3) break; // para tudo console.log(n); // imprime 1 e 2 }
A combinação que aparece em tela é continue com filtro: pular o item que não
interessa e seguir com o resto, sem aninhar o código em if dentro de if.
O exemplo mostra os dois em laços separados. Com break, saem duas linhas —
com break: 1 e com break: 2 — e o 3 nunca aparece, porque é ele que
dispara a saída. Com continue em n % 2 === 0, saem três:
com continue (impares): 1, 3 e 5. Os pares 2 e 4 foram pulados sem
imprimir, e o laço seguiu até o fim.
O laço vazio — laço sem corpo, só ponto e vírgula — é código válido e bug:
o for roda o número de vezes pedido sem executar nada, e i++ some. Aparece
quando alguém deixa a chave de um bloco vazio de lado.
O detalhe do forEach que costuma ser lembrado na hora errada: o break não
sai dele para a lista inteira. Ele sai do laço interno da função que o
forEach chama, e o forEach continua com o próximo item. Para parar a lista
inteira de verdade, é for...of com break — assunto da aula seguinte.
A ordem do laço com break dentro de função
Um break dentro de uma função não sai do for que está chamando a função:
ele sai do laço mais interno que existe dentro daquela função, e se não houver
nenhum, é erro de sintaxe. É por isso que forEach não aceita break para
parar tudo — o break só sai do forEach.
Quando a intenção é parar a volta inteira, o laço é for...of com break, e
find devolve o item em vez de parar.
A função comBreakInterno do exemplo é exatamente esse caso. Ela tem um
for...of próprio sobre a lista de tarefas, e o break dentro dele sai desse
for, não de nenhum laço de fora — a chamada sai em
break dentro da funcao: estudar. Com 'treinar' na entrada, a volta 0 não
casou com o break, deu return item e devolveu 'estudar' na hora; o
treinar nunca chegou a ser comparado.
Exemplo
// for com contador e forEach, que e o laco que percorre o array. const tarefas = ['estudar', 'treinar', 'descansar']; /* for: o laco tem contador */ for (let i = 0; i < 3; i++) { console.log('i =', i); } // let dentro do for nao sobrevive ao laco: no fim das voltas o nome sumiu try { console.log('i vale', i); } catch (erro) { console.log('depois do laco:', erro.constructor.name, '-', erro.message); } /* o < fica e nao o <=: com <= seriam quatro voltas */ for (let i = 0; i <= 2; i++) console.log('volta', i); /* o passo nao precisa ser i++ */ for (let i = 2; i <= 8; i += 2) console.log('de dois em dois:', i); for (let i = 3; i > 0; i--) console.log('descendo:', i); /* forEach: o array controla o numero de voltas */ tarefas.forEach((tarefa, indice) => { console.log('forEach:', indice, tarefa); }); /* o escopo de cada volta e proprio */ const nomes = []; for (const item of tarefas) { const marcado = item + '!'; nomes.push(marcado); } console.log('marcados:', nomes); /* forEach nao devolve nada */ const resultado = tarefas.forEach((t) => t.length); console.log('retorno do forEach:', resultado); /* break sai do laco inteiro */ for (const n of [1, 2, 3, 4, 5]) { if (n === 3) break; console.log('com break:', n); } /* continue pula para a proxima volta */ for (const n of [1, 2, 3, 4, 5]) { if (n % 2 === 0) continue; // pula os pares console.log('com continue (impares):', n); } /* o contador de dentro do laco, que e o que o app usa para numerar a lista */ let total = 0; tarefas.forEach(() => { total = total + 1; }); console.log('quantas tarefas:', total); /* break dentro de uma funcao nao sai do for que a chamou */ function comBreakInterno(n) { for (const item of tarefas) { if (item === n) break; // sai deste for, nao do de fora return item; } return 'nao achou'; } console.log('break dentro da funcao:', comBreakInterno('treinar'));
Saída real
i = 0 i = 1 i = 2 depois do laco: ReferenceError - i is not defined volta 0 volta 1 volta 2 de dois em dois: 2 de dois em dois: 4 de dois em dois: 6 de dois em dois: 8 descendo: 3 descendo: 2 descendo: 1 forEach: 0 estudar forEach: 1 treinar forEach: 2 descansar marcados: [ 'estudar!', 'treinar!', 'descansar!' ] retorno do forEach: undefined com break: 1 com break: 2 com continue (impares): 1 com continue (impares): 3 com continue (impares): 5 quantas tarefas: 3 break dentro da funcao: estudar
while, for...of e for...in
while, for...of e for...in
A condição do laço é o que decide se ele repete. while repete enquanto a
condição for verdadeira, e a condição é conferida antes
de cada volta — igual ao for. A diferença é que o while não tem contador
pronto: se o passo estiver dentro do bloco, é preciso escrever.
let i = 0; while (i < 3) { console.log(i); i++; }
Esquecer o i++ produz laço infinito: o programa não volta a fazer nada
nunca, e o aparelho trava. Esse é o defeito mais caro do while, e a razão de o
for existir. O while serve quando o número de voltas não se sabe antes.
O exemplo imprime while: 0, while: 1 e while: 2 — três voltas para a mesma
condição do for da aula anterior, escritas em quatro linhas em vez de uma. O
i++ está na segunda linha do bloco, e é ele que impede a quarta volta: sem ele,
a condição i < 3 continuaria verdadeira para sempre.
A diferença para o for não está na forma de testar, e sim em onde fica o
passo: no for ele é uma das três partes do cabeçalho e não pode ser esquecido
por descuido visual; no while ele é uma linha do corpo, e o esquecimento
compila sem erro nenhum. É por isso que while em código de app costuma vir
acompanhado de um contador com nome óbvio.
No fim do exemplo há o while aplicado ao dado, que é o uso legítimo: um
contador restantes que começa em 3 e desce, imprimindo faltam 3 tarefas,
faltam 2 tarefas e faltam 1 tarefas. O laço termina quando restantes chega
a 0. Aqui o número de voltas é desconhecido no começo — depende do 3 — e
ainda assim a forma é a mesma do for com contador.
do...while confere a condição depois de executar, então o bloco roda pelo
menos uma vez, mesmo com condição já falsa:
let tentativas = 0; do { tentativas++; } while (tentativas < 3); console.log(tentativas); // 3
Com a condição já falsa na entrada, do...while dá 1 e while dá 0.
O exemplo deixa essa comparação na mesma linha, e é a forma mais direta de ver a
diferença:
do while com condicao falsa: 1 | while nem roda: 0. O 1 vem do
jaFalso++ executado antes de o JavaScript olhar o while (false); o 0 vem
da função anônima do lado direito, em que o while (false) nunca entra no
corpo. A linha anterior é o outro lado:
do while com condicao verdadeira: 3, com três voltas porque a condição só
falha na quarta.
O uso de do...while que aparece em app é a tentativa: rodar a ação, conferir o
resultado, repetir. do...while é o que garante que a primeira tentativa
existe, mesmo quando a condição já começa falsa.
for...of: o laço que percorre valor
for...of percorre um iterável — array, string, conjunto — e entrega o
valor, não a posição:
for (const item of ['estudar', 'treinar']) { console.log(item); }
Para array e string, for...of é a escolha certa: o valor vem pronto, o nome da
variável diz o que ele é, e const funciona porque o valor nunca é reatribuído.
É o laço que o aluno vai escrever ao percorrer a lista de tarefas no componente.
O protocolo por trás é o Symbol.iterator, o mesmo que permite o for...of
funcionar em qualquer estrutura iterável.
O exemplo percorre os dois tipos na mesma seção. Sobre a lista de três tarefas,
imprime for...of: estudar, for...of: treinar e for...of: descansar — três
valores, e nenhum índice. Sobre a string 'Ana', imprime letra: A, letra: n e
letra: a: o for...of dividiu o texto em caracteres, e cada volta recebeu
um. Nenhum dos dois laços precisou de .length, .split nem contador.
Os dois laços de break e continue também estão no exemplo, e mostram que o
for...of é o laço que aceita os dois. Com break, sai uma linha só, porque
'treinar' interrompe. Com continue, saem duas — e 'treinar' foi o pulado. É
o mesmo comportamento da aula anterior, agora em cima do for...of.
for...in: o laço que percorre chave
for...in percorre os nomes das propriedades, e o nome sempre chega como
string. Em array, isso é quase sempre o que se não quer:
for (const chave in ['a', 'b']) { console.log(chave, typeof chave); // '0' string, '1' string }
A pegadinha clássica de for...in em array: o índice chega como string, e
qualquer conta feita com ele passa por conversão implícita. for...in é para
objeto, e mesmo nele a ordem das chaves não é garantida.
O exemplo passa a lista pelo for...in e imprime
for...in em array: 0 string, 1 string e 2 string. A palavra string no
fim de cada linha é o ponto: são três linhas que mostram exatamente o defeito.
O for...of da seção anterior imprimiu os valores; o for...in imprime as
posições — e como texto.
Em objeto ele funciona como se deve, e a linha sai completa:
for...in em objeto: nome = Ana, idade = 30 e ativo = true. Cada volta
recebeu a chave e leu o valor com usuario[chave]. Perceba que o acesso é por
colchete: com o valor da chave é a única forma de ler a propriedade em
variável.
A terceira passagem repete o for...in no array e imprime
indice do for...in: string | item pela chave: estudar, e o mesmo para
treinar e descansar. Ou seja: o item continua correto quando lido por
chave — o que quebra é quando o índice entra na conta. tarefas[chave] devolve
o texto certo; tarefas[chave] + 1 ou chave - 1 já fariam conversão
implícita silenciosa.
E a alternativa moderna sai nas três últimas linhas do grupo:
Object.keys: nome, Object.keys: idade, Object.keys: ativo. O
for...of sobre Object.keys(usuario) entrega as chaves como valores de
verdade, e o nome da variável é chave de novo — sem a conversão implícita e
com a lista de chaves disponível para o find e o includes depois.
| Precisa de | Use |
|---|---|
| o valor de um array ou string | for...of |
| a chave de um objeto | for...in, ou Object.keys() |
parar com break no meio | for...of |
| número de voltas conhecido | for com contador |
break e continue funcionam nos três. E forEach não aceita break para
parar tudo, porque o break sai do laço mais interno da função que o chamou e
forEach é um método, não um laço.
A linha da tabela que decide a escolha é a do break: for...of e for são
laços de verdade, e o break neles sai da lista inteira. A exceção é o
forEach, que não é laço — é método que recebe função, e o break que estiver
dentro dessa função sai do laço que ela tiver, não da lista do forEach.
É por isso que o exemplo do break usa for...of e não forEach, mesmo
percorriendo a mesma lista de tarefas: o parou em: estudar é a prova de que o
laço parou na primeira volta que não era a procurada, e não de que a lista
terminou.
Exemplo
// while, do while, for...of e for...in: os quatro lacos. let i = 0; while (i < 3) { console.log('while:', i); i++; } // do while confere a condicao depois: roda pelo menos uma vez let tentativas = 0; do { tentativas++; } while (tentativas < 3); console.log('do while com condicao verdadeira:', tentativas); let jaFalso = 0; do { jaFalso++; } while (false); console.log('do while com condicao falsa:', jaFalso, '| while nem roda:', (() => { let k = 0; while (false) { k++; } return k; })()); /* for...of entrega o VALOR */ const tarefas = ['estudar', 'treinar', 'descansar']; for (const item of tarefas) { console.log('for...of:', item); } /* for...of tambem percorre string */ for (const letra of 'Ana') console.log('letra:', letra); /* break e continue funcionam no for...of */ for (const item of tarefas) { if (item === 'treinar') break; console.log('parou em:', item); } for (const item of tarefas) { if (item === 'treinar') continue; console.log('pulou:', item); } /* for...in entrega a CHAVE, e a chave e string */ for (const chave in tarefas) { console.log('for...in em array:', chave, typeof chave); } /* for...in em objeto: o uso correto dele */ const usuario = { nome: 'Ana', idade: 30, ativo: true }; for (const chave in usuario) { console.log('for...in em objeto:', chave, '=', usuario[chave]); } /* a pegadinha: dentro do for...in, o indice e string */ for (const chave in tarefas) { console.log('indice do for...in:', typeof chave, '| item pela chave:', tarefas[chave]); } /* a alternativa moderna a for...in: Object.keys */ for (const chave of Object.keys(usuario)) { console.log('Object.keys:', chave); } /* laco com condicao que depende do dado: while e a escolha natural */ let restantes = 3; while (restantes > 0) { console.log('faltam', restantes, 'tarefas'); restantes--; }
Saída real
while: 0 while: 1 while: 2 do while com condicao verdadeira: 3 do while com condicao falsa: 1 | while nem roda: 0 for...of: estudar for...of: treinar for...of: descansar letra: A letra: n letra: a parou em: estudar pulou: estudar pulou: descansar for...in em array: 0 string for...in em array: 1 string for...in em array: 2 string for...in em objeto: nome = Ana for...in em objeto: idade = 30 for...in em objeto: ativo = true indice do for...in: string | item pela chave: estudar indice do for...in: string | item pela chave: treinar indice do for...in: string | item pela chave: descansar Object.keys: nome Object.keys: idade Object.keys: ativo faltam 3 tarefas faltam 2 tarefas faltam 1 tarefas