Dia 5 — Condicionais
if, else if e else
if, else if e else
if executa um bloco de código quando a condição é verdadeira. A condição não
precisa ser booleana: o JavaScript testa se o valor é truthy, e qualquer um dos
sete valores falsy conta como falso.
if (idade >= 18) { console.log('maior de idade'); }
else é o caminho de quando a condição é falsa, e else if é um meio-termo
quando existem mais de dois caminhos. A ordem importa: o JavaScript testa de cima
para baixo e executa o primeiro que for verdadeiro, ignorando o resto.
if (total === 0) { console.log('vazio'); } else if (total < 10) { console.log('poucos'); } else { console.log('muitos'); }
O exemplo roda essa cadeia com total valendo 7 e imprime uma linha só:
poucos itens: 7. Uma linha, não três — e é esse o comportamento do else if.
A primeira condição falhou, a segunda passou, e a terceira nunca foi olhada. Com
total em 900 a mesma cadeia imprimiria a linha do else, e com 0 a do
primeiro.
A função faixa no fim do exemplo é a mesma ideia com quatro caminhos, e o
console imprime o resultado das quatro de uma vez:
faixa: vazio | poucos | muitos | demais. Ela roda sobre [0, 7, 42, 900], e
cada número cai num caminho diferente. É a forma que o app usa para classificar
quantidade sem switch e sem else grudado.
As chaves não são detalhe de escrita
O if sem chaves é o defeito mais silencioso. O bloco é o que está entre { }, e as chaves são obrigatórias quando há mais
de um comando. Sem elas, o JavaScript considera apenas a próxima instrução como
corpo:
// isso e um bloco so if (total > 0) console.log('tem itens'); console.log('esta linha roda sempre'); // nao esta dentro do if
O console.log do segundo está fora do if: a indentação sugerindo o contrário é
só visual. Esse é o bug mais silencioso de if sem chaves, porque o código roda e
o resultado sai errado. Escreva sempre as chaves.
As duas linhas do exemplo aparecem lado a lado, e é a ordem delas que denuncia o
problema: dentro do if vem primeiro, e `fora do if: roda sempre, a indentacao
aqui e so visual vem em seguida. Se a segunda linha estivesse dentro do if`,
ela sairia antes — porque o if imprime primeiro. A segunda linha saiu depois
porque ninguém a escolheu: ela está no nível do if, e o if é só uma
instrução.
A mesma demonstração do parágrafo sobre condição não booleana está no exemplo, e
o console mostra exatamente um dos dois lados rodando. if ('' && 'ana') não
imprime nada: a string vazia é falsy e o && devolve '', que é falsy. Na
linha seguinte if ([0]) imprime array vazio e truthy, este bloco roda — o
array [0] tem um item, mas mesmo [] seria truthy, porque objeto é truthy. A
ausência de uma das duas linhas no terminal é parte da prova.
Condição aninhada
Aninhar é colocar um if dentro do outro. Funciona, e em geral é sinal de que
o teste pode ser reescrito com uma condição só, usando && e ||:
// aninhado if (idade >= 18) { if (documento === true) { console.log('pode retirar'); } } // mesma regra em uma condicao so if (idade >= 18 && documento === true) { console.log('pode retirar'); }
A cadeia de if é a forma de tratar vários casos do mesmo dado. O early return é o nome técnico do caminho mais limpo, quando o valor tem
nomes definidos — sair
da função assim que a condição decide o resultado:
function mensagemDoStatus(status) { if (status === 'pendente') return 'ainda falta'; if (status === 'concluida') return 'pronto'; return 'desconhecido'; }
Cada if com return elimina um nível de indentação. É o motivo de a forma ser
preferida mesmo quando o aninhamento "caberia": a função tem uma saída por vez e
não existe else grudado.
O exemplo chama mensagemDoStatus com os três valores de uma vez e imprime
status: ainda falta | pronto | desconhecido. Cada nome saiu de um if
diferente, e o terceiro saiu do return que sobrou no fim — que é o que acontece
com qualquer status que a função não conhece. Sem esse return final, o
terceiro valor devolveria undefined, e o join escreveria a palavra
undefined no meio da linha.
O aninhado e o && também aparecem lado a lado, com a mesma resposta:
aninhado: pode retirar e depois com &&: pode retirar. Duas linhas, mesmo
resultado, formas diferentes. O && ganha quando os dois testes são sobre o
mesmo dado; o aninhamento continua sendo o certo quando o segundo teste só faz
sentido depois que o primeiro passou.
A indentação conta para quem lê
Indentação é o espaço no começo da linha que mostra o que está dentro de quê. O
JavaScript ignora os espaços — o que define o bloco são as chaves — mas quem lê o
código depende do recuo para entender a estrutura. Um arquivo com indentação
inconsistente não está errado para o JavaScript e está errado para quem precisa
entender o if às onze da noite.
O exemplo é a prova de que o JavaScript ignora o recuo: a linha
fora do if: roda sempre, a indentacao aqui e so visual está alinhada com o
if e não com o corpo, e o console a mostra como quem é — uma linha que sempre
roda. Se a indentação decidisse o bloco, essa linha nunca sairia.
Exemplo
// if, else if e else. O exemplo mostra a cadeia de condicao, o bloco com // chaves e o retorno antecipado. const total = 7; if (total === 0) { console.log('vazio'); } else if (total < 10) { console.log('poucos itens:', total); } else { console.log('muitos itens:', total); } // a condicao nao precisa ser booleana: o JavaScript testa truthy e falsy if ('' && 'ana') { console.log('string vazia e falsy, este bloco nao roda'); } if ([0]) { console.log('array vazio e truthy, este bloco roda'); } // sem chaves, so a proxima instrucao e o corpo do if if (total > 0) console.log('dentro do if'); console.log('fora do if: roda sempre, a indentacao aqui e so visual'); // condicao aninhada const idade = 20; const documento = true; if (idade >= 18) { if (documento === true) { console.log('aninhado: pode retirar'); } } // a mesma regra, em uma condicao so if (idade >= 18 && documento === true) { console.log('com &&: pode retirar'); } // cadeia de if com retorno antecipado: uma saida por vez function mensagemDoStatus(status) { if (status === 'pendente') return 'ainda falta'; if (status === 'concluida') return 'pronto'; return 'desconhecido'; } console.log('status:', ['pendente', 'concluida', 'outro'].map(mensagemDoStatus).join(' | ')); // a funcao que classifica a quantidade, que e o que o app vai usar function faixa(total) { if (total === 0) return 'vazio'; else if (total < 10) return 'poucos'; else if (total < 100) return 'muitos'; else return 'demais'; } console.log('faixa:', [0, 7, 42, 900].map(faixa).join(' | '));
Saída real
poucos itens: 7 array vazio e truthy, este bloco roda dentro do if fora do if: roda sempre, a indentacao aqui e so visual aninhado: pode retirar com &&: pode retirar status: ainda falta | pronto | desconhecido faixa: vazio | poucos | muitos | demais
Operador ternário e switch
Operador ternário e switch
O operador ternário é a condição escrita em uma expressão. São três partes na
ordem: a condição, o valor se verdadeiro, o valor se falso — separadas por ? e
:. Equivale a um if com else que devolve valor em vez de executar
comandos:
const situacao = total > 0 ? 'tem itens' : 'vazio';
O que define o ternário é ser expressão. Ele pode aparecer dentro de outra
expressão, coisa que o if não faz:
console.log(`${total} tarefa${total === 1 ? '' : 's'}`);
Essa linha é a razão de o ternário existir no React Native: montar o texto que
vai dentro do Text com plural, sem if espalhado pelo JSX.
O exemplo imprime as três formas do ternário, e a segunda é a que interessa:
plural dentro do texto: 3 tarefas. O ${total === 1 ? '' : 's'} devolveu
's', e o resultado é o texto montado inteiro — sem variável no meio, sem if
antes. A primeira linha, ternario: tem itens, é o ternário dos três campos
isolado; a terceira, ternario de true/false: sim, mostra o caso mais curto, em
que a condição já é comparação e os dois lados são palavras.
A armadilha do ternário aninhado
Ternário aninhado é permitido, mas fica ilegível rápido. Quando o valor tem mais
de dois nomes possíveis, o objeto de tradução é melhor — o switch logo abaixo
faz o mesmo com menos ruído:
const precos = { basico: 10, pro: 20 }; console.log(precos[plano]);
O exemplo usa essa forma e imprime preco do plano: 20: o objeto precos é a
tabela, e o plano é o índice. Com quatro planos, o ternário aninhado vira
quatro linhas de ? : dentro de ? :; com o objeto, vira quatro linhas de
chave e valor, e o console.log é o mesmo. O switch da seção seguinte faz o
mesmo serviço quando o valor tem casos que executam código, e não só devolvem
um valor.
switch: o caso com nome
switch compara um valor contra vários case. Cada case precisa terminar em
break. Sem ele a execução cai no caso seguinte — o termo é fallthrough:
switch (status) { case 'pendente': console.log('ainda falta'); break; case 'concluida': console.log('pronto'); break; default: console.log('desconhecido'); }
O default é o caminho de quando nenhum case casa, e cumpre o papel do else.
O teste do switch é comparação estrita (===), então '1' não casa com
1.
O fallthrough raramente é erro de digitação e sempre é uma pergunta: ele
funciona de verdade quando um caso compartilha a execução com o seguinte, e o
comentário dizendo isso é obrigatório:
switch (nivel) { case 1: case 2: console.log('iniciante'); break; }
Os dois case são o fallthrough proposital: nível 1 e nível 2 imprimem a mesma
coisa. Sem o break, um case que não acaba em comando cai no próximo — e é
assim que o bug aparece, quando alguém acrescenta um case no meio e não percebe
que o anterior não tem break.
O exemplo separa os dois fallthroughs em duas funções, porque eles não são o
mesmo defeito. descreverStatus usa break em todos os case e a linha sai
switch: ainda falta | pronto | desconhecido — três entradas, três saídas
diferentes. cairNoSeguinte não usa nenhum break, e com o número 2 o
console imprime fallthrough: doistres.
O resultado doistres é o ponto inteiro da demonstração. A entrada foi 2, e a
saída tem palavras de três casos: passou por 'um' (sem executar, porque o
case 1 não casou), por 'dois' (casou, e caiu) e por 'tres' (caiu de novo).
Uma entrada, três trechos executados — é o que o fallthrough significa.
A terceira função é o uso legítimo: nivel(1) e nivel(2) caem no mesmo
return, e a linha sai case compartilhado: iniciante | iniciante | sem nivel.
Os dois case empilhados, sem comando entre eles, é o fallthrough proposital: o
case 1 não faz nada e deixa o case 2 responder.
Os três operadores de atalho: && || e ??
O ?? é o coalescente, nome técnico de quem escolhe o valor quando falta um.
Os três não comparam nada; devolvem um dos dois lados. Cada um resolve um caso
diferente, e confundir || com ?? é o erro mais comum dos três:
| Operador | Devolve o primeiro se... | Usado para | ||||
|---|---|---|---|---|---|---|
| `\ | \ | ` | for truthy | `valor \ | \ | padrao` |
&& | for falsy | usuario && usuario.nome | ||||
?? | for null ou undefined | config ?? padrao |
A diferença entre || e ?? aparece com o zero:
console.log(0 || 'padrão'); // 'padrão' <- 0 foi trocado console.log(0 ?? 'padrão'); // 0 <- 0 foi preservado
|| troca o zero e a string vazia, porque são falsy. ?? só troca null e
undefined, que são os dois casos em que o valor realmente "não existe". Em
configuração e em valor vindo de API, ?? é o certo.
O && é o atalho de teste: se o valor da esquerda for falsy, o resultado é o
próprio valor da esquerda, e o ponto final evita acessar o que não existe:
console.log(usuario && usuario.nome);
O exemplo compara os três operadores com os valores que os diferenciam, e a
tabela acima aparece em três linhas. A primeira é o || com zero e com texto:
|| com zero: padrao | || com texto: Ana — o 0 foi trocado pelo padrão e o
'Ana' passou intacto. A segunda é o ?? com os três casos:
?? com zero: 0 | ?? com null: padrao | ?? com vazio: — o 0 foi preservado, o
null foi trocado, e a string vazia passou intacta, que é a diferença do ??
para o || em duas palavras.
O && aparece nas duas linhas seguintes, e a segunda é a que salva o app de um
erro: && evita o erro quando nao ha usuario: null. O lado esquerdo é null, e
o lado direito — null.nome, que seria um TypeError — nunca foi avaliado. É
isso que o && faz: ele devolve o valor da esquerda quando ela é falsy, e o
JavaScript não chega a olhar o resto.
A cadeia completa sai em cadeia de &&: 3: usuario é truthy, usuario.nome
é truthy, e usuario.nome.length vale 3. Três truthy encadeados, resultado
numérico. A última linha do exemplo faz o oposto do &&, que é somar || até
sair o primeiro valor truthy: primeiro valor truthy: final — '', 0 e
null foram descartados, e 'final' ganhou.
Exemplo
// Operador ternario, switch e os operadores de atalho. const total = 3; const status = 'pendente'; const usuario = { nome: 'Ana', notificacoes: 0 }; /* ternario: uma condicao escrita como expressao */ console.log('ternario:', total > 0 ? 'tem itens' : 'vazio'); console.log('plural dentro do texto:', `${total} tarefa${total === 1 ? '' : 's'}`); console.log('ternario de true/false:', 1 > 0 ? 'sim' : 'nao'); /* objeto de traducao: melhor que ternario aninhado */ const precos = { basico: 10, pro: 20 }; const plano = 'pro'; console.log('preco do plano:', precos[plano]); /* switch: o caso com nome, e o que acontece sem break */ function descreverStatus(atual) { let saida = ''; switch (atual) { case 'pendente': saida += 'ainda falta'; break; case 'concluida': saida += 'pronto'; break; default: saida += 'desconhecido'; } return saida; } console.log('switch:', descreverStatus('pendente'), '|', descreverStatus('concluida'), '|', descreverStatus('outro')); // sem break, a execucao cai no case seguinte: o fallthrough function cairNoSeguinte(n) { let r = ''; switch (n) { case 1: r += 'um'; case 2: r += 'dois'; case 3: r += 'tres'; } return r; } console.log('fallthrough:', cairNoSeguinte(2)); // fallthrough proposital: dois case compartilham o mesmo comando function nivel(n) { switch (n) { case 1: case 2: return 'iniciante'; case 3: return 'avancado'; default: return 'sem nivel'; } } console.log('case compartilhado:', nivel(1), '|', nivel(2), '|', nivel(9)); /* os tres operadores de atalho */ console.log('|| com zero:', 0 || 'padrao', '| || com texto:', 'Ana' || 'padrao'); console.log('?? com zero:', 0 ?? 'padrao', '| ?? com null:', null ?? 'padrao', '| ?? com vazio:', '' ?? 'padrao'); console.log('&& curto:', usuario && usuario.nome); console.log('&& evita o erro quando nao ha usuario:', null && null.nome); /* o encadeamento util no app */ console.log('cadeia de &&:', usuario && usuario.nome && usuario.nome.length); console.log('primeiro valor truthy:', '' || 0 || null || 'final');
Saída real
ternario: tem itens plural dentro do texto: 3 tarefas ternario de true/false: sim preco do plano: 20 switch: ainda falta | pronto | desconhecido fallthrough: doistres case compartilhado: iniciante | iniciante | sem nivel || com zero: padrao | || com texto: Ana ?? com zero: 0 | ?? com null: padrao | ?? com vazio: && curto: Ana && evita o erro quando nao ha usuario: null cadeia de &&: 3 primeiro valor truthy: final