Skip to content

Capítulo 13 — JavaScript Essencial

Vídeo curto explicativo (link será adicionado posteriormente)


13.1 — JavaScript no navegador: contexto e papel

Vídeo curto explicativo (link será adicionado posteriormente)

O JavaScript é a linguagem de programação nativa do navegador web — o único ambiente de execução de código que opera diretamente no cliente, sem necessidade de instalação ou plugin. Criado em 1995 por Brendan Eich na Netscape Communications em apenas dez dias, o JavaScript foi concebido originalmente como uma linguagem de scripting para adicionar comportamento interativo simples a páginas HTML. Nas três décadas seguintes, evoluiu para uma das linguagens de programação mais amplamente utilizadas do mundo, presente em navegadores, servidores, dispositivos embarcados e aplicações de desktop.

13.1.1 — O que é JavaScript e sua história

JavaScript é uma linguagem interpretada, dinamicamente tipada, multiparadigma — suportando programação imperativa, orientada a objetos (com protótipos) e funcional — e single-threaded (executada em uma única thread). Sua especificação formal é mantida pela ECMA International sob o nome ECMAScript (ES). A versão ES5 (2009) modernizou a linguagem; o ES6/ES2015 representou a maior atualização da história, introduzindo let, const, arrow functions, classes, Promises, módulos e dezenas de outras funcionalidades. Desde então, novas versões são publicadas anualmente.

Nota terminológica: JavaScript e ECMAScript são frequentemente usados como sinônimos. Tecnicamente, ECMAScript é a especificação; JavaScript é a implementação da especificação pelo navegador (e pelo Node.js). Ao longo deste capítulo, usaremos JavaScript como termo geral.

A padronização da linguagem foi motivada por uma guerra de incompatibilidades nos anos 1990, quando diferentes navegadores implementavam versões conflitantes. A criação do padrão ECMAScript pelo consórcio ECMA em 1997 iniciou a convergência que tornaria o JavaScript universal.

13.1.2 — JavaScript no navegador vs Node.js: o mesmo idioma, contextos diferentes

Na disciplina de Introdução à Programação, você provavelmente trabalhou com JavaScript no contexto do Node.js — um ambiente de execução JavaScript no servidor, criado em 2009, que permitiu usar a linguagem fora do navegador. É importante compreender que Node.js e o navegador compartilham o mesmo núcleo da linguagem (ECMAScript), mas operam em contextos com APIs completamente diferentes:

JavaScript no Navegador JavaScript no Node.js
Ambiente Navegador (Chrome, Firefox...) Servidor / terminal
Objeto global window global / globalThis
APIs exclusivas DOM, BOM, Fetch, Web APIs fs, http, path, process
Acesso ao sistema Restrito (sandbox) Amplo (arquivos, rede, SO)
Entrada do usuário Eventos de mouse, teclado, formulários process.stdin, argumentos
Saída visual DOM — manipula HTML/CSS Terminal / arquivos

O núcleo da linguagem — tipos de dados, funções, arrays, objetos, loops, condicionais, Promises, async/await — é idêntico em ambos os contextos. O que muda são as APIs disponíveis para interagir com o ambiente.

Neste capítulo e nos seguintes, o foco é o JavaScript no navegador: como ele interage com o HTML via DOM, como responde a eventos do usuário e como busca dados de servidores via Fetch API.

13.1.3 — Como o navegador executa JavaScript: o event loop e a thread única

Compreender o modelo de execução do JavaScript no navegador é fundamental para evitar erros comuns e escrever código assíncrono corretamente.

O JavaScript é single-threaded — executa em uma única thread, processando uma operação por vez. Isso significa que não há paralelismo nativo: enquanto um bloco de código está em execução, nenhum outro código JavaScript pode ser executado simultaneamente.

O mecanismo que permite ao JavaScript lidar com operações demoradas (requisições de rede, timers, eventos do usuário) sem bloquear a interface é o event loop:

┌─────────────────────────────────┐
│         Call Stack              │  ← onde o código é executado
│  (pilha de chamadas de função)  │
└────────────────┬────────────────┘
                 │
┌────────────────▼────────────────┐
│           Event Loop            │  ← monitora stack + queue
└────────────────┬────────────────┘
                 │
┌────────────────▼────────────────┐
│         Callback Queue          │  ← callbacks aguardando execução
│  (eventos, timers, fetch...)    │
└─────────────────────────────────┘

O funcionamento é: quando uma operação assíncrona (como fetch() ou setTimeout()) é iniciada, ela é delegada ao navegador (Web APIs). Quando completa, seu callback é colocado na fila (queue). O event loop verifica continuamente: se a call stack estiver vazia, pega o próximo callback da fila e o executa.

Consequência prática: código JavaScript que bloqueia a call stack por muito tempo (loops longos, computação pesada) trava a interface do usuário — o navegador não consegue processar eventos nem re-renderizar a página enquanto a stack não esvazia. Por isso, operações demoradas devem ser assíncronas.

13.1.4 — Como incluir JavaScript no HTML: <script>, defer e async

Existem três formas de incluir JavaScript em um documento HTML, cada uma com comportamento de carregamento distinto:

Inline (no corpo do HTML):

<script>
  console.log('Executado imediatamente ao ser encontrado pelo parser');
</script>

Arquivo externo — sem atributos (bloqueante):

<!-- O parser HTML pausa, baixa e executa o script, depois continua -->
<script src="js/script.js"></script>

Arquivo externo — com defer (recomendado):

<!-- Download paralelo; executa apenas após o HTML ser completamente parseado -->
<script src="js/script.js" defer></script>

Arquivo externo — com async:

<!-- Download paralelo; executa imediatamente quando o download termina
     (pode interromper o parsing do HTML) -->
<script src="js/script.js" async></script>

Imagem sugerida: diagrama comparativo mostrando a linha do tempo de parsing HTML, download e execução do script para os três comportamentos (sem atributo, defer, async) — ilustrando visualmente por que defer é a escolha mais segura para scripts que dependem do DOM.

(imagem será adicionada posteriormente)

Regra prática: use sempre defer para scripts que interagem com o DOM. Use async apenas para scripts completamente independentes (como analytics). Nunca coloque scripts sem atributos no <head> — coloque-os antes de </body> ou use defer.

<!-- Padrão recomendado -->
<head>
  <meta charset="UTF-8" />
  <title>Página</title>
  <link rel="stylesheet" href="css/style.css" />
  <!-- defer: baixa em paralelo, executa após o DOM estar pronto -->
  <script src="js/app.js" defer></script>
</head>

13.1.5 — O console do navegador como ambiente de aprendizado

O Console do DevTools (F12 → aba Console) é o ambiente interativo mais imediato para experimentar JavaScript. Ele funciona como um REPL (Read-Eval-Print Loop) — você digita uma expressão, pressiona Enter, e o resultado é exibido imediatamente.

// Exemplos para experimentar no console do navegador

// Saída no console
console.log('Olá, navegador!');
console.warn('Aviso');
console.error('Erro');
console.table([{ nome: 'Ana', nota: 9 }, { nome: 'Bruno', nota: 7 }]);

// Expressões são avaliadas imediatamente
2 + 2          // → 4
'olá' + ' mundo' // → "olá mundo"
typeof 42      // → "number"

// Variáveis persistem durante a sessão
let x = 10;
x * 3;         // → 30

13.2 — Variáveis, tipos e operadores

Vídeo curto explicativo (link será adicionado posteriormente)

13.2.1 — var, let e const: diferenças e boas práticas

JavaScript possui três palavras-chave para declaração de variáveis, com comportamentos distintos em relação a escopo, reatribuição e hoisting:

// var — escopo de função, sofre hoisting, evitar em código moderno
var nome = 'Ana';
var nome = 'Bruno'; // redeclaração permitida — fonte de bugs

// let — escopo de bloco, pode ser reatribuída, não pode ser redeclarada
let contador = 0;
contador = 1;       // reatribuição permitida
// let contador = 2; // erro: já foi declarada neste escopo

// const — escopo de bloco, não pode ser reatribuída após a declaração
const PI = 3.14159;
// PI = 3;           // erro: assignment to constant variable

// IMPORTANTE: const não significa "imutável" — significa "não reatribuível"
// Objetos e arrays declarados com const podem ter seu conteúdo alterado
const usuario = { nome: 'Ana', idade: 20 };
usuario.idade = 21;   // permitido: alterando propriedade
// usuario = {};       // erro: reatribuindo a variável

Regra prática moderna: - Use const por padrão para tudo - Use let apenas quando a variável precisar ser reatribuída (contadores, acumuladores) - Nunca use var em código novo — seu comportamento de escopo e hoisting é fonte de bugs

var let const
Escopo Função Bloco Bloco
Hoisting Sim (com undefined) Sim (TDZ) Sim (TDZ)
Reatribuição Sim Sim Não
Redeclaração Sim Não Não

13.2.2 — Tipos primitivos

JavaScript possui seis tipos primitivos e um tipo de objeto:

// string — sequência de caracteres
const nome = 'Maria';
const mensagem = "Olá, mundo!";
const template = `Olá, ${nome}!`; // template literal

// number — todos os números (inteiros e decimais)
const inteiro = 42;
const decimal = 3.14;
const negativo = -7;
const infinito = Infinity;
const naoNumero = NaN; // Not a Number — resultado de operação inválida

// boolean
const ativo = true;
const arquivado = false;

// null — ausência intencional de valor
const semValor = null;

// undefined — variável declarada mas não inicializada
let naoInicializada;
console.log(naoInicializada); // → undefined

// symbol — identificador único (uso avançado)
const id = Symbol('id');

// bigint — inteiros arbitrariamente grandes
const grandeNumero = 9007199254740991n;

// typeof: verifica o tipo de um valor
typeof 'texto'    // → "string"
typeof 42         // → "number"
typeof true       // → "boolean"
typeof undefined  // → "undefined"
typeof null       // → "object" ← bug histórico da linguagem
typeof {}         // → "object"
typeof []         // → "object"
typeof function(){} // → "function"

13.2.3 — Tipagem dinâmica e coerção de tipos

JavaScript é dinamicamente tipado — o tipo de uma variável é determinado pelo valor que ela contém em determinado momento, não por uma declaração explícita. Uma mesma variável pode conter diferentes tipos ao longo da execução.

A coerção de tipos (type coercion) é a conversão automática entre tipos realizada pelo JavaScript em determinadas operações — um comportamento que frequentemente surpreende desenvolvedores iniciantes:

// Coerção implícita — acontece automaticamente
'5' + 3      // → "53"  (number coercido para string)
'5' - 3      // → 2     (string coercida para number)
'5' * '3'    // → 15    (ambas coercidas para number)
true + 1     // → 2     (true vira 1)
false + 1    // → 1     (false vira 0)
null + 1     // → 1     (null vira 0)
undefined + 1 // → NaN

// Comparação com coerção (==) vs sem coerção (===)
5 == '5'    // → true  (coerção: '5' vira 5)
5 === '5'   // → false (sem coerção: tipos diferentes)
null == undefined  // → true
null === undefined // → false

// Conversão explícita — sempre preferível à implícita
Number('42')        // → 42
Number('')          // → 0
Number('abc')       // → NaN
String(42)          // → "42"
Boolean(0)          // → false
Boolean('')         // → false
Boolean(null)       // → false
Boolean(undefined)  // → false
Boolean(NaN)        // → false
Boolean([])         // → true  (array vazio é truthy!)
Boolean({})         // → true  (objeto vazio é truthy!)
parseInt('42px')    // → 42
parseFloat('3.14m') // → 3.14

Boa prática: sempre use === (igualdade estrita) e !== em vez de == e !=. A igualdade estrita não realiza coerção de tipos, produzindo resultados mais previsíveis.

13.2.4 — Operadores aritméticos, de comparação e lógicos

// ── Aritméticos ──
10 + 3   // → 13
10 - 3   // → 7
10 * 3   // → 30
10 / 3   // → 3.3333...
10 % 3   // → 1  (resto da divisão)
10 ** 3  // → 1000 (exponenciação)

// Incremento e decremento
let x = 5;
x++;    // x = 6 (pós-incremento)
++x;    // x = 7 (pré-incremento)
x--;    // x = 6
--x;    // x = 5

// Atribuição composta
x += 10;  // x = x + 10
x -= 5;   // x = x - 5
x *= 2;   // x = x * 2
x /= 4;   // x = x / 4
x **= 2;  // x = x ** 2
x %= 3;   // x = x % 3

// ── Comparação ──
5 > 3     // → true
5 < 3     // → false
5 >= 5    // → true
5 <= 4    // → false
5 === 5   // → true  (igualdade estrita — recomendada)
5 !== 3   // → true  (desigualdade estrita)

// ── Lógicos ──
true && true    // → true   (E lógico)
true && false   // → false
false || true   // → true   (OU lógico)
false || false  // → false
!true           // → false  (NÃO lógico)
!false          // → true

// Short-circuit evaluation
false && expressaoCara()  // expressaoCara() NÃO é chamada
true  || expressaoCara()  // expressaoCara() NÃO é chamada

// Uso prático do short-circuit
const nome = usuario.nome || 'Anônimo'; // fallback se nome for falsy
const exibir = estaLogado && renderizarPerfil(); // executa apenas se logado

13.2.5 — Template literals

Template literals são strings delimitadas por backticks (`) que permitem interpolação de expressões e strings multilinhas:

const nome = 'Ana';
const nota = 9.5;

// Interpolação de variáveis e expressões
const mensagem = `Olá, ${nome}! Sua nota foi ${nota}.`;
// → "Olá, Ana! Sua nota foi 9.5."

const calculo = `Resultado: ${10 * 3 + 5}`;
// → "Resultado: 35"

// Expressões complexas
const aprovado = `${nome} foi ${nota >= 7 ? 'aprovada' : 'reprovada'}.`;
// → "Ana foi aprovada."

// String multilinha — sem necessidade de \n
const html = `
  <article class="card">
    <h2>${nome}</h2>
    <p>Nota: ${nota}</p>
  </article>
`;

13.2.6 — Nullish coalescing (??) e optional chaining (?.)

Dois operadores modernos do ES2020 que simplificam o tratamento de valores nulos:

// Nullish coalescing (??) — retorna o lado direito apenas se o esquerdo
// for null ou undefined (diferente de ||, que reage a qualquer falsy)
const nome = null ?? 'Anônimo';        // → "Anônimo"
const porto = undefined ?? 'Maceió';   // → "Maceió"
const zero = 0 ?? 42;                  // → 0  (|| retornaria 42)
const vazio = '' ?? 'padrão';          // → '' (|| retornaria 'padrão')

// Optional chaining (?.) — acessa propriedades sem lançar erro se o objeto
// for null ou undefined
const usuario = null;
const cidade = usuario?.endereco?.cidade; // → undefined (sem erro)
// sem ?.: usuario.endereco lançaria TypeError

const usuarios = [{ nome: 'Ana' }, null];
const primeiroNome = usuarios[0]?.nome;  // → "Ana"
const segundoNome  = usuarios[1]?.nome;  // → undefined (sem erro)

// Combinando ?? com ?.
const cidade = usuario?.endereco?.cidade ?? 'Cidade não informada';

13.3 — Funções e escopo

Vídeo curto explicativo (link será adicionado posteriormente)

13.3.1 — Declaração de função vs expressão de função

// Declaração de função (function declaration)
// Sofre hoisting completo — pode ser chamada antes de ser declarada
function saudacao(nome) {
  return `Olá, ${nome}!`;
}

console.log(saudacao('Maria')); // → "Olá, Maria!"

// Expressão de função (function expression)
// NÃO sofre hoisting — deve ser declarada antes de ser chamada
const saudacao = function(nome) {
  return `Olá, ${nome}!`;
};

// Função nomeada — útil para debug e recursão
const fatorial = function calcularFatorial(n) {
  return n <= 1 ? 1 : n * calcularFatorial(n - 1);
};

13.3.2 — Arrow functions

Arrow functions são uma sintaxe concisa para funções introduzida no ES6. Além da brevidade, possuem uma diferença semântica importante em relação ao this — relevante no contexto do DOM (Capítulo 14):

// Sintaxe básica
const dobrar = (n) => n * 2;
const somar = (a, b) => a + b;

// Parênteses opcionais com um único parâmetro
const quadrado = n => n * n;

// Sem parâmetros: parênteses obrigatórios
const saudacao = () => 'Olá!';

// Corpo com múltiplas linhas: chaves e return explícito
const calcularImc = (peso, altura) => {
  const imc = peso / (altura * altura);
  return imc.toFixed(2);
};

// Retorno de objeto literal: envolva em parênteses
const criarUsuario = (nome, idade) => ({ nome, idade });
// sem parênteses, as chaves seriam interpretadas como corpo da função

// Arrow functions são ideais como callbacks
const numeros = [1, 2, 3, 4, 5];
const dobrados = numeros.map(n => n * 2);     // → [2, 4, 6, 8, 10]
const pares    = numeros.filter(n => n % 2 === 0); // → [2, 4]

13.3.3 — Parâmetros padrão e rest parameters

// Parâmetros padrão (default parameters)
function cumprimentar(nome, saudacao = 'Olá') {
  return `${saudacao}, ${nome}!`;
}

cumprimentar('Ana');           // → "Olá, Ana!"
cumprimentar('Ana', 'Bem-vinda'); // → "Bem-vinda, Ana!"

// Rest parameters (...) — agrupa argumentos extras em um array
function somar(...numeros) {
  return numeros.reduce((total, n) => total + n, 0);
}

somar(1, 2, 3)       // → 6
somar(1, 2, 3, 4, 5) // → 15

// Combinando parâmetros normais com rest
function log(nivel, ...mensagens) {
  console.log(`[${nivel}]`, ...mensagens);
}

log('INFO', 'Servidor iniciado', 'porta 3000');
// → [INFO] Servidor iniciado porta 3000

13.3.4 — Escopo: global, de função e de bloco

O escopo determina onde uma variável é acessível. JavaScript possui três níveis:

// Escopo global — acessível em qualquer lugar
const APP_NOME = 'PWEB1';

function demonstrar() {
  // Escopo de função — acessível apenas dentro da função
  const local = 'variável local';
  console.log(APP_NOME); // → 'PWEB1' (acessa escopo global)
  console.log(local);    // → 'variável local'

  if (true) {
    // Escopo de bloco (let e const) — acessível apenas dentro do bloco
    let bloco = 'variável de bloco';
    var funcao = 'variável de função'; // var ignora o bloco!
    console.log(bloco);  // → 'variável de bloco'
  }

  // console.log(bloco); // ReferenceError: bloco is not defined
  console.log(funcao); // → 'variável de função' (var ignora o bloco)
}

// console.log(local); // ReferenceError: local is not defined

13.3.5 — Hoisting

Hoisting é o comportamento pelo qual declarações de funções e variáveis são "elevadas" para o topo do seu escopo antes da execução:

// Declarações de função sofrem hoisting completo
console.log(somar(2, 3)); // → 5 — funciona antes da declaração!

function somar(a, b) {
  return a + b;
}

// var sofre hoisting mas é inicializado como undefined
console.log(x); // → undefined (não lança erro)
var x = 10;
console.log(x); // → 10

// let e const sofrem hoisting mas ficam na Temporal Dead Zone (TDZ)
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 20;

// Expressões de função NÃO sofrem hoisting
// console.log(multiplicar(2, 3)); // TypeError: multiplicar is not a function
const multiplicar = (a, b) => a * b;

13.3.6 — Closures: conceito e casos de uso práticos

Uma closure é a capacidade de uma função de "lembrar" e acessar variáveis do escopo onde foi criada, mesmo após esse escopo ter encerrado sua execução. É um dos conceitos mais poderosos e fundamentais do JavaScript:

// Exemplo fundamental de closure
function criarContador() {
  let contagem = 0; // variável do escopo externo

  return function() {
    contagem++;
    return contagem;
  };
}

const contador = criarContador();
console.log(contador()); // → 1
console.log(contador()); // → 2
console.log(contador()); // → 3
// 'contagem' é inacessível diretamente, mas a função interna a "lembra"

// Closure com parâmetro — fábrica de funções
function multiplicadorDe(fator) {
  return (numero) => numero * fator;
}

const dobrar  = multiplicadorDe(2);
const triplicar = multiplicadorDe(3);

dobrar(5)    // → 10
triplicar(5) // → 15

// Caso de uso prático: encapsulamento de estado
function criarCarrinho() {
  const itens = []; // privado — inacessível externamente

  return {
    adicionar(produto) { itens.push(produto); },
    remover(nome) {
      const idx = itens.findIndex(i => i.nome === nome);
      if (idx !== -1) itens.splice(idx, 1);
    },
    total() { return itens.reduce((s, i) => s + i.preco, 0); },
    listar() { return [...itens]; }
  };
}

const carrinho = criarCarrinho();
carrinho.adicionar({ nome: 'Curso JS', preco: 99 });
carrinho.adicionar({ nome: 'Livro CSS', preco: 49 });
console.log(carrinho.total()); // → 148

13.4 — Arrays e objetos

Vídeo curto explicativo (link será adicionado posteriormente)

13.4.1 — Arrays: criação, acesso e métodos essenciais

// Criação
const frutas = ['maçã', 'banana', 'laranja'];
const numeros = [1, 2, 3, 4, 5];
const misto = [1, 'texto', true, null, { chave: 'valor' }];
const vazio = [];
const tamanho5 = new Array(5).fill(0); // → [0, 0, 0, 0, 0]

// Acesso por índice (começa em 0)
frutas[0]  // → 'maçã'
frutas[2]  // → 'laranja'
frutas.at(-1) // → 'laranja' (último elemento — ES2022)

// Propriedade length
frutas.length // → 3

// Adição e remoção
frutas.push('uva');       // adiciona ao final → ['maçã','banana','laranja','uva']
frutas.pop();             // remove do final → ['maçã','banana','laranja']
frutas.unshift('morango');// adiciona ao início → ['morango','maçã','banana','laranja']
frutas.shift();           // remove do início → ['maçã','banana','laranja']

// splice: remove/insere em posição específica
frutas.splice(1, 1);              // remove 1 elemento a partir do índice 1
frutas.splice(1, 0, 'pêra', 'kiwi'); // insere sem remover

// slice: extrai sem modificar o original
const primeiras = frutas.slice(0, 2);  // cópia dos 2 primeiros
const ultimas   = frutas.slice(-2);    // cópia dos 2 últimos

// indexOf e includes
frutas.indexOf('banana')   // → 1 (ou -1 se não encontrar)
frutas.includes('laranja') // → true

// join e split
frutas.join(', ')             // → "maçã, banana, laranja"
'a,b,c'.split(',')            // → ['a', 'b', 'c']

// sort e reverse
[3,1,4,1,5].sort((a, b) => a - b) // → [1, 1, 3, 4, 5]
frutas.reverse()                   // inverte no lugar

13.4.2 — Métodos funcionais: map, filter, reduce, find, some, every

Estes métodos são fundamentais no JavaScript moderno — especialmente para manipular dados vindos de APIs:

const alunos = [
  { nome: 'Ana',   nota: 9.5, aprovado: true  },
  { nome: 'Bruno', nota: 5.0, aprovado: false },
  { nome: 'Carla', nota: 8.2, aprovado: true  },
  { nome: 'Diego', nota: 6.8, aprovado: true  },
];

// map — transforma cada elemento, retorna novo array de mesmo tamanho
const nomes = alunos.map(a => a.nome);
// → ['Ana', 'Bruno', 'Carla', 'Diego']

const notas = alunos.map(a => a.nota);
// → [9.5, 5.0, 8.2, 6.8]

const resumos = alunos.map(a =>
  `${a.nome}: ${a.aprovado ? '✓' : '✗'}`
);
// → ['Ana: ✓', 'Bruno: ✗', 'Carla: ✓', 'Diego: ✓']

// filter — retorna novo array com elementos que satisfazem a condição
const aprovados = alunos.filter(a => a.aprovado);
// → [{Ana}, {Carla}, {Diego}]

const notaAlta = alunos.filter(a => a.nota >= 8);
// → [{Ana}, {Carla}]

// reduce — acumula todos os elementos em um único valor
const somaNotas = alunos.reduce((soma, a) => soma + a.nota, 0);
// → 29.5

const mediaNotas = somaNotas / alunos.length;
// → 7.375

// Reduce para construir objeto
const porNome = alunos.reduce((acc, a) => {
  acc[a.nome] = a.nota;
  return acc;
}, {});
// → { Ana: 9.5, Bruno: 5.0, Carla: 8.2, Diego: 6.8 }

// find — retorna o PRIMEIRO elemento que satisfaz a condição (ou undefined)
const primeiroAprovado = alunos.find(a => a.aprovado);
// → { nome: 'Ana', nota: 9.5, aprovado: true }

const alunoInexistente = alunos.find(a => a.nome === 'Eva');
// → undefined

// findIndex — como find, mas retorna o índice
const idxBruno = alunos.findIndex(a => a.nome === 'Bruno'); // → 1

// some — verdadeiro se PELO MENOS UM elemento satisfaz a condição
const algumReprovado = alunos.some(a => !a.aprovado); // → true

// every — verdadeiro se TODOS os elementos satisfazem a condição
const todosAprovados = alunos.every(a => a.aprovado);  // → false

// flat e flatMap
const matriz = [[1, 2], [3, 4], [5]];
matriz.flat()    // → [1, 2, 3, 4, 5]

const frasesComPalavras = ['olá mundo', 'bom dia'];
frasesComPalavras.flatMap(f => f.split(' '));
// → ['olá', 'mundo', 'bom', 'dia']

// Encadeamento de métodos — muito comum na prática
const mediasAprovados = alunos
  .filter(a => a.aprovado)
  .map(a => a.nota)
  .reduce((soma, nota, _, arr) => soma + nota / arr.length, 0);
// média das notas apenas dos aprovados

13.4.3 — Objetos: criação, acesso e manipulação

// Criação de objeto literal
const usuario = {
  nome: 'Maria Silva',
  idade: 22,
  email: 'maria@exemplo.com',
  ativo: true,
  endereco: {              // objeto aninhado
    cidade: 'Maceió',
    estado: 'AL'
  },
  hobbies: ['leitura', 'programação'], // array como propriedade
  saudacao() {             // método
    return `Olá, sou ${this.nome}`;
  }
};

// Acesso a propriedades
usuario.nome              // → 'Maria Silva' (notação de ponto)
usuario['email']          // → 'maria@exemplo.com' (notação de colchetes)
usuario.endereco.cidade   // → 'Maceió'
usuario.hobbies[0]        // → 'leitura'
usuario.saudacao()        // → 'Olá, sou Maria Silva'

// Propriedade dinâmica (nome da propriedade em variável)
const campo = 'nome';
usuario[campo]            // → 'Maria Silva'

// Adição e modificação de propriedades
usuario.curso = 'Sistemas de Informação'; // adiciona
usuario.idade = 23;                       // modifica

// Remoção
delete usuario.ativo;

// Verificação de existência
'nome' in usuario          // → true
usuario.hasOwnProperty('curso') // → true

// Shorthand property — quando variável e chave têm o mesmo nome
const nome = 'João';
const idade = 25;
const pessoa = { nome, idade }; // equivale a { nome: nome, idade: idade }

// Computed property names — nome de chave dinâmico
const chave = 'cor';
const objeto = { [chave]: 'azul' }; // → { cor: 'azul' }

13.4.4 — Desestruturação de arrays e objetos

A desestruturação (destructuring) permite extrair valores de arrays e objetos de forma concisa:

// Desestruturação de array
const coordenadas = [10, 20, 30];
const [x, y, z] = coordenadas;
// x → 10, y → 20, z → 30

// Ignorando elementos
const [primeiro, , terceiro] = coordenadas;
// primeiro → 10, terceiro → 30

// Com valor padrão
const [a = 0, b = 0, c = 0, d = 0] = [1, 2];
// a→1, b→2, c→0, d→0

// Troca de variáveis sem temporária
let p = 1, q = 2;
[p, q] = [q, p]; // p→2, q→1

// Desestruturação de objeto
const usuario = { nome: 'Ana', idade: 22, cidade: 'Maceió' };
const { nome, idade } = usuario;
// nome → 'Ana', idade → 22

// Com renomeação
const { nome: nomeUsuario, cidade: localidade } = usuario;
// nomeUsuario → 'Ana', localidade → 'Maceió'

// Com valor padrão
const { nome: n, curso = 'Não informado' } = usuario;
// n → 'Ana', curso → 'Não informado'

// Desestruturação aninhada
const { endereco: { cidade, estado } } = {
  endereco: { cidade: 'Maceió', estado: 'AL' }
};

// Desestruturação em parâmetros de função
function exibirUsuario({ nome, idade, curso = 'SI' }) {
  return `${nome}, ${idade} anos — ${curso}`;
}

exibirUsuario({ nome: 'Ana', idade: 22 });
// → "Ana, 22 anos — SI"

13.4.5 — Spread operator e rest em objetos

// Spread em arrays — expande elementos
const a = [1, 2, 3];
const b = [4, 5, 6];
const combinado = [...a, ...b]; // → [1, 2, 3, 4, 5, 6]
const copia = [...a];           // cópia superficial

// Spread em objetos — copia e/ou mescla propriedades
const base = { tema: 'claro', idioma: 'pt-BR' };
const extensao = { idioma: 'en-US', fonte: 'Inter' };

const configuracao = { ...base, ...extensao };
// → { tema: 'claro', idioma: 'en-US', fonte: 'Inter' }
// 'idioma' de extensao sobrescreve o de base

// Cópia com modificação (padrão imutável — muito usado com estado)
const usuarioAtualizado = { ...usuario, idade: 23 };
// cria novo objeto com todos os campos, mas idade = 23

// Rest em objetos — captura o restante
const { nome, ...restante } = usuario;
// nome → 'Ana'
// restante → { idade: 22, cidade: 'Maceió' }

13.4.6 — Métodos estáticos: Object.keys(), Object.values(), Object.entries()

const produto = {
  nome: 'Notebook',
  preco: 3500,
  estoque: 12
};

// Object.keys — array de chaves
Object.keys(produto)    // → ['nome', 'preco', 'estoque']

// Object.values — array de valores
Object.values(produto)  // → ['Notebook', 3500, 12]

// Object.entries — array de pares [chave, valor]
Object.entries(produto)
// → [['nome','Notebook'], ['preco',3500], ['estoque',12]]

// Iterando sobre um objeto com for...of + entries
for (const [chave, valor] of Object.entries(produto)) {
  console.log(`${chave}: ${valor}`);
}

// Object.assign — copia propriedades
const destino = {};
Object.assign(destino, produto, { desconto: 10 });
// equivale a spread: { ...produto, desconto: 10 }

// Object.freeze — torna objeto imutável
const CONFIG = Object.freeze({ API_URL: 'https://api.exemplo.com' });
// CONFIG.API_URL = 'outra'; — silenciosamente ignorado (ou erro em strict mode)

// Object.fromEntries — o inverso de entries
const mapa = [['a', 1], ['b', 2], ['c', 3]];
Object.fromEntries(mapa) // → { a: 1, b: 2, c: 3 }

// Transformando objeto via entries + map
const precosComDesconto = Object.fromEntries(
  Object.entries(produto)
    .filter(([k]) => k === 'preco')
    .map(([k, v]) => [k, v * 0.9])
);

13.5 — Condicionais e loops

Vídeo curto explicativo (link será adicionado posteriormente)

13.5.1 — if, else if, else e operador ternário

const nota = 7.5;

// if/else if/else
if (nota >= 9) {
  console.log('Excelente');
} else if (nota >= 7) {
  console.log('Aprovado');
} else if (nota >= 5) {
  console.log('Recuperação');
} else {
  console.log('Reprovado');
}

// Operador ternário — para expressões simples
const resultado = nota >= 7 ? 'Aprovado' : 'Reprovado';

// Ternário aninhado (use com moderação — prejudica legibilidade)
const conceito = nota >= 9 ? 'A' : nota >= 7 ? 'B' : nota >= 5 ? 'C' : 'D';

// Condicional com truthy/falsy
const nome = '';
const exibicao = nome || 'Anônimo'; // → 'Anônimo' (nome é falsy)

// Guardas (early return) — evitam aninhamento excessivo
function processar(valor) {
  if (!valor) return null;          // guarda: retorna cedo se inválido
  if (valor < 0) return 0;          // guarda: retorna cedo se negativo
  return valor * 2;                 // lógica principal sem aninhamento
}

13.5.2 — switch

const diaSemana = 3;

switch (diaSemana) {
  case 1:
    console.log('Segunda-feira');
    break;
  case 2:
    console.log('Terça-feira');
    break;
  case 3:
  case 4:
    console.log('Quarta ou Quinta-feira'); // fallthrough intencional
    break;
  case 5:
    console.log('Sexta-feira');
    break;
  default:
    console.log('Fim de semana');
}

// switch com strings
const comando = 'iniciar';

switch (comando) {
  case 'iniciar':
    iniciar();
    break;
  case 'parar':
    parar();
    break;
  default:
    console.warn(`Comando desconhecido: ${comando}`);
}

13.5.3 — for, while e do...while

// for — quando o número de iterações é conhecido
for (let i = 0; i < 5; i++) {
  console.log(i); // → 0, 1, 2, 3, 4
}

// Iterando sobre array com índice
const frutas = ['maçã', 'banana', 'laranja'];
for (let i = 0; i < frutas.length; i++) {
  console.log(`${i}: ${frutas[i]}`);
}

// while — quando a condição de parada não é conhecida previamente
let tentativas = 0;
let acertou = false;

while (!acertou && tentativas < 3) {
  tentativas++;
  const resposta = obterResposta();
  if (resposta === 'correta') acertou = true;
}

// do...while — executa pelo menos uma vez antes de verificar a condição
let entrada;
do {
  entrada = solicitarEntrada();
} while (!entradaValida(entrada));

13.5.4 — for...of e for...in

// for...of — itera sobre valores de iteráveis (arrays, strings, Maps, Sets)
const numeros = [10, 20, 30];
for (const numero of numeros) {
  console.log(numero); // → 10, 20, 30
}

// Com string
for (const letra of 'IFAL') {
  console.log(letra); // → I, F, A, L
}

// Com desestruturação e entries
for (const [indice, valor] of numeros.entries()) {
  console.log(`${indice}: ${valor}`);
}

// for...in — itera sobre chaves enumeráveis de objetos
// (use com cautela em arrays)
const configuracao = { tema: 'escuro', idioma: 'pt', fonte: 16 };

for (const chave in configuracao) {
  console.log(`${chave}: ${configuracao[chave]}`);
}
// → tema: escuro | idioma: pt | fonte: 16

// for...of vs for...in em arrays
const arr = ['a', 'b', 'c'];
for (const v of arr) console.log(v);   // → a, b, c  (valores)
for (const k in arr) console.log(k);   // → 0, 1, 2  (índices como strings)
// Prefira for...of para arrays

13.5.5 — break e continue

// break — interrompe o loop imediatamente
const numeros = [1, 5, 3, 8, 2, 9, 4];
let primeiraMaiorQue6;

for (const n of numeros) {
  if (n > 6) {
    primeiraMaiorQue6 = n;
    break; // para o loop ao encontrar o primeiro
  }
}
// → primeiraMaiorQue6 = 8

// continue — pula para a próxima iteração
const resultado = [];
for (let i = 0; i <= 10; i++) {
  if (i % 2 !== 0) continue; // pula ímpares
  resultado.push(i);
}
// → [0, 2, 4, 6, 8, 10]

// Labels — para break/continue em loops aninhados
externo: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (j === 1) continue externo; // continua o loop externo
    console.log(i, j);
  }
}

13.6 — Assincronia: introdução

Vídeo curto explicativo (link será adicionado posteriormente)

13.6.1 — Por que JavaScript é assíncrono

Como apresentado na seção 13.1.3, o JavaScript é single-threaded — executa em uma única thread. Operações que demoram para completar — requisições de rede, leitura de arquivos, timers — não podem simplesmente bloquear essa thread aguardando o resultado, pois isso congelaria a interface do usuário.

A solução é o modelo assíncrono: em vez de esperar, o JavaScript inicia a operação, registra o que deve ser feito quando ela completar (um callback), e continua executando o restante do código. Quando a operação completa, o callback é chamado.

// Exemplo de assincronia: setTimeout
console.log('1 — antes');

setTimeout(() => {
  console.log('3 — dentro do timeout (após 1 segundo)');
}, 1000);

console.log('2 — depois');

// Saída:
// 1 — antes
// 2 — depois
// 3 — dentro do timeout (após 1 segundo)

O código após o setTimeout executa imediatamente, sem esperar o timer. Quando o segundo passa, o callback é colocado na fila e executado após a call stack esvaziar.

13.6.2 — Callbacks: o padrão original

O callback é o padrão mais básico de assincronia: uma função passada como argumento para ser chamada quando uma operação assíncrona completa:

// Callback simples
function buscarDados(id, callback) {
  setTimeout(() => { // simula requisição de rede
    const dados = { id, nome: 'Produto ' + id, preco: 99.90 };
    callback(null, dados); // convenção: (erro, resultado)
  }, 500);
}

buscarDados(1, (erro, produto) => {
  if (erro) {
    console.error('Erro:', erro);
    return;
  }
  console.log(produto);
});

// Callback hell — problema clássico com callbacks aninhados
buscarUsuario(id, (erro, usuario) => {
  if (erro) return tratarErro(erro);
  buscarPedidos(usuario.id, (erro, pedidos) => {
    if (erro) return tratarErro(erro);
    buscarProdutos(pedidos[0].id, (erro, produto) => {
      if (erro) return tratarErro(erro);
      // aninhamento torna o código ilegível e difícil de manter
    });
  });
});

O "callback hell" — aninhamento profundo de callbacks — foi o principal motivador para a criação das Promises.

13.6.3 — Promises: conceito, estados e encadeamento

Uma Promise representa o resultado eventual de uma operação assíncrona. Em vez de passar um callback, a função assíncrona retorna uma Promise que pode estar em um de três estados:

  • Pending: operação em andamento
  • Fulfilled: operação completou com sucesso (tem um valor)
  • Rejected: operação falhou (tem um motivo de erro)
// Criando uma Promise
function buscarDados(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, nome: 'Produto ' + id }); // sucesso
      } else {
        reject(new Error('ID inválido')); // falha
      }
    }, 500);
  });
}

// Consumindo com .then() e .catch()
buscarDados(1)
  .then(produto => {
    console.log('Sucesso:', produto);
    return produto.nome; // retorno é passado para o próximo .then()
  })
  .then(nome => {
    console.log('Nome:', nome);
  })
  .catch(erro => {
    console.error('Erro:', erro.message);
  })
  .finally(() => {
    console.log('Operação concluída'); // sempre executado
  });

// Promise.all — aguarda múltiplas promises em paralelo
Promise.all([
  buscarDados(1),
  buscarDados(2),
  buscarDados(3),
]).then(([p1, p2, p3]) => {
  console.log(p1, p2, p3);
}).catch(erro => {
  // falha se QUALQUER uma rejeitar
  console.error(erro);
});

// Promise.allSettled — aguarda todas, independente de sucesso ou falha
Promise.allSettled([buscarDados(1), buscarDados(-1)])
  .then(resultados => {
    resultados.forEach(r => {
      if (r.status === 'fulfilled') console.log('OK:', r.value);
      else console.log('Erro:', r.reason.message);
    });
  });

13.6.4 — async/await: sintaxe síncrona para código assíncrono

async/await é uma sintaxe introduzida no ES2017 que permite escrever código assíncrono com aparência síncrona — tornando-o mais legível e próximo do fluxo de pensamento linear:

// async: declara que a função retorna uma Promise
// await: pausa a execução da função até a Promise resolver

async function carregarPerfil(userId) {
  // await pausa aqui até buscarUsuario resolver
  const usuario = await buscarUsuario(userId);
  const pedidos = await buscarPedidos(usuario.id);
  const ultimoPedido = await buscarPedido(pedidos[0].id);

  return {
    usuario,
    totalPedidos: pedidos.length,
    ultimoPedido
  };
}

// Equivale ao encadeamento de .then() mas muito mais legível
// Compare com o callback hell da seção 13.6.2

// Consumindo uma função async
carregarPerfil(1)
  .then(perfil => console.log(perfil))
  .catch(erro => console.error(erro));

// await também funciona no nível do módulo (top-level await)

13.6.5 — Tratamento de erros com try/catch

async function carregarDados(id) {
  try {
    const usuario = await buscarUsuario(id);
    const pedidos = await buscarPedidos(usuario.id);
    return { usuario, pedidos };

  } catch (erro) {
    // captura qualquer erro nas operações await acima
    console.error('Falha ao carregar dados:', erro.message);
    return null; // ou relançar: throw erro;

  } finally {
    // executado sempre, independente de sucesso ou erro
    esconderIndicadorDeCarregamento();
  }
}

// Tratamento de erros específicos
async function salvar(dados) {
  try {
    const resposta = await fetch('/api/salvar', {
      method: 'POST',
      body: JSON.stringify(dados)
    });

    if (!resposta.ok) {
      // fetch não rejeita em erros HTTP — é necessário verificar
      throw new Error(`HTTP ${resposta.status}: ${resposta.statusText}`);
    }

    return await resposta.json();

  } catch (erro) {
    if (erro instanceof TypeError) {
      // TypeError do fetch: sem conexão, URL inválida
      console.error('Erro de rede:', erro.message);
    } else {
      console.error('Erro ao salvar:', erro.message);
    }
    throw erro; // relança para o chamador decidir
  }
}

Referências: - MDN — JavaScript - MDN — JavaScript Guide - ECMAScript — Especificação oficial - javascript.info — O Tutorial Moderno de JavaScript - You Don't Know JS (livro gratuito)


Atividades — Capítulo 13

1. Por que é recomendado usar === em vez de == para comparações em JavaScript?

2. Qual é a diferença entre map() e filter()?

3. O que é uma closure em JavaScript?

4. Qual é a vantagem de async/await em relação ao encadeamento de .then()?

  • GitHub Classroom: Implementar um módulo JavaScript que: (1) processe um array de alunos usando filter, map e reduce para calcular médias e classificar aprovados; (2) implemente uma função com closure para criar um placar de pontuação; (3) use async/await com setTimeout para simular uma operação assíncrona de busca de dados. (link será adicionado)

:material-arrow-left: Voltar ao Capítulo 12 — Framework CSS: Tailwind CSS :material-arrow-right: Ir ao Capítulo 14 — Manipulação do DOM