Guia comprehensive da linguagem JavaScript moderna — do modelo de execução (event loop, call stack, microtasks) aos recursos mais recentes do ES2025/ES2026. Para um senior fullstack, dominar JavaScript significa entender como a linguagem realmente funciona por baixo, não apenas a sintaxe. Para tipagem estática, ver TypeScript. Para concorrência e Node.js-specific, ver Node.js. Para testes, ver Testes em JavaScript.
O que é
JavaScript é uma linguagem dinâmica, fracamente tipada, interpretada (JIT compiled), com garbage collection e closures de primeira classe. Criada por Brendan Eich em 1995 em 10 dias, padronizada como ECMAScript (ECMA-262). Roda no browser, servidor (Node.js, Deno, Bun), edge (Cloudflare Workers), mobile (React Native), e até microcontroladores.
Em 2026, é a linguagem mais usada do mundo, e TypeScript é a primeira linguagem do GitHub. Entender JavaScript profundamente é pré-requisito para produtividade real no ecossistema.
Em entrevistas, o que diferencia um senior em JavaScript:
Entender o event loop — macrotasks, microtasks, render phase, starvation
Dominar closures, this e prototypes — os 3 conceitos mais mal compreendidos
JavaScript é single-threaded com event loop. Isso significa: uma thread executa seu código, operações assíncronas vão para filas, e o event loop decide o que rodar em seguida.
Call stack — pilha de frames de execução. Cada chamada de função empilha um frame; cada return desempilha.
Heap — onde objetos, closures, funções e arrays vivem. Gerenciado pelo GC.
Task queue (macrotask) — fila de “tasks” pendentes: callbacks de setTimeout, setInterval, I/O do Node, eventos DOM.
Microtask queue — fila de maior prioridade: Promise.then, queueMicrotask, MutationObserver.
Event loop — o loop que orquestra tudo. Ciclo simplificado:
Pega a próxima macrotask do queue e executa até o call stack esvaziar
Processa todas as microtasks acumuladas (drena a queue)
(Browser) Faz render/layout/paint se necessário
Volta ao passo 1
Consequência crítica: microtasks têm prioridade. Se você enfileira microtasks dentro de microtasks, elas rodam antes da próxima macrotask. Isso pode causar starvation da macrotask queue.
var a = 1; // function-scoped, hoisted, pode redeclararlet b = 2; // block-scoped, temporal dead zone, não pode redeclararconst c = 3; // como let, mas não pode reassignar
var — evite em código novo.
function exemplo() { console.log(x); // undefined (hoisted) var x = 10; if (true) { var y = 20; } console.log(y); // 20 — var vaza para fora do bloco}
let e const — default moderno.
{ let x = 10;}console.log(x); // ReferenceError — block-scoped// Temporal Dead Zoneconsole.log(a); // ReferenceErrorlet a = 5;
const não é imutável — apenas a reference é imutável. Objetos continuam mutáveis.
const arr = [1, 2, 3];arr.push(4); // OK — mutação permitida// arr = []; // erro — reassign proibido// Para imutabilidade real, use Object.freeze ou ImmerObject.freeze(arr);arr.push(5); // silenciosamente ignorado (ou erro em strict mode)
Escopo lexical
Funções capturam variáveis do escopo onde foram definidas, não onde são chamadas.
function criarContador() { let count = 0; return function() { count++; return count; };}const contador = criarContador();contador(); // 1contador(); // 2contador(); // 3// 'count' continua vivo por causa do closure
Closures — o conceito mais fundamental
Closure é uma função que “lembra” o escopo onde foi criada. Todas as funções em JavaScript são closures.
Uso prático 1 — encapsulamento
function criarConta(saldoInicial) { let saldo = saldoInicial; // "privado" return { depositar(valor) { if (valor <= 0) throw new Error('valor inválido'); saldo += valor; }, sacar(valor) { if (valor > saldo) throw new Error('saldo insuficiente'); saldo -= valor; }, consultarSaldo() { return saldo; } };}const conta = criarConta(100);conta.depositar(50);conta.consultarSaldo(); // 150// conta.saldo → undefined (não existe)
Uso prático 2 — memoization
function memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (!cache.has(key)) { cache.set(key, fn.apply(this, args)); } return cache.get(key); };}const fibMemo = memoize(function fib(n) { return n < 2 ? n : fibMemo(n - 1) + fibMemo(n - 2);});
Armadilha clássica — loop + closure + var
// BUG clássicofor (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0);}// Imprime: 3, 3, 3// Solução moderna — letfor (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0);}// Imprime: 0, 1, 2
Memory leak via closure
Closure mantém referências vivas no heap. Cuidado com closures que capturam objetos grandes.
function processarDadosGrandes() { const dadosEnormes = carregarMuitosDados(); // 100 MB const resultado = calcular(dadosEnormes); return function mostrarResultado() { console.log(resultado); // dadosEnormes continua no heap até este closure ser GC'd };}
this — o “monstro de sete cabeças”
O this em JavaScript é determinado em tempo de execução, não em tempo de declaração. O valor depende de como a função foi chamada.
As 5 regras de binding
1. Default binding — função normal
function foo() { console.log(this);}foo(); // window (ou global em Node) em sloppy mode // undefined em strict mode ('use strict')
function Pessoa(nome) { this.nome = nome; // this é o novo objeto}const maria = new Pessoa('Maria');
5. Arrow functions — herdam this do escopo léxico
const obj = { name: 'Maria', saudar() { setTimeout(function() { console.log(this.name); // undefined — this é global/undefined }, 100); }, saudarCerto() { setTimeout(() => { console.log(this.name); // "Maria" — arrow herda this de saudarCerto }, 100); }};
Arrow functions não têm this próprio, e também não têm arguments, super, ou new.target. Não podem ser construtores.
Perdendo this em callbacks
class Contador { count = 0; increment() { this.count++; }}const c = new Contador();button.addEventListener('click', c.increment); // this = button, não c!// Soluçõesbutton.addEventListener('click', () => c.increment()); // arrow mantém cbutton.addEventListener('click', c.increment.bind(c)); // bind fixa this// Ou arrow method (class field)class Contador2 { count = 0; increment = () => { this.count++; // this fixado por instância };}
Prototypes e herança
JavaScript tem prototype-based inheritance, não class-based (apesar da sintaxe class).
O que é prototype
Cada objeto tem um link interno ([[Prototype]]) para outro objeto. Quando você acessa uma propriedade que não existe, JS sobe pela cadeia de prototypes.
function Pessoa(nome) { this.nome = nome;}Pessoa.prototype.saudar = function() { return `Olá, ${this.nome}`;};const maria = new Pessoa('Maria');maria.saudar(); // "Olá, Maria"// saudar não é cópia — existe uma vez em Pessoa.prototype
"5" + 3 // "53" — + vira concatenação se há string"5" - 3 // 2 — - sempre aritmético[] + [] // ""[] + {} // "[object Object]"
Regras de == (loose equality):
null == undefined → true
null == 0 → false
"" == false → true
[] == false → true
[] == ![] → true (!)
NaN == NaN → false (!)
Regra prática:sempre use ===. == só é útil para if (x == null) que pega null e undefined.
Falsy values
// Valores falsy em JavaScript:false0-00n""nullundefinedNaN// Tudo mais é truthy, incluindo:[] // array vazio é truthy!{} // objeto vazio é truthy!"0" // string "0" é truthy!"false" // string "false" é truthy!
NaN e floats
0.1 + 0.2 === 0.3; // false — IEEE 7540.1 + 0.2; // 0.30000000000000004// Para dinheiro, NÃO use number// Use BigInt em centavos ou biblioteca decimal.js
BigInt (ES2020)
const big = 9007199254740993n; // note o 'n'const huge = BigInt(Number.MAX_SAFE_INTEGER) + 1n;// Não pode misturar com numberbig + 1; // TypeErrorbig + 1n; // OK
arr.map(fn) // transformaarr.filter(fn) // filtraarr.reduce(fn, init) // reduzarr.find(fn) // primeiro que satisfazarr.findIndex(fn)arr.findLast(fn) // ES2023arr.some(fn) // algum satisfaz?arr.every(fn) // todos satisfazem?arr.includes(x)arr.indexOf(x)arr.slice(start, end) // cópia parcialarr.concat(other)arr.flat(depth)arr.flatMap(fn)arr.join(sep)arr.at(-1) // ES2022, suporta negativo
Métodos mutáveis (modificam original)
arr.push(x) // adiciona no fimarr.pop() // remove do fimarr.shift() // remove do inícioarr.unshift(x) // adiciona no inícioarr.splice(start, deleteCount, ...items)arr.sort((a, b) => ...) // in-placearr.reverse() // in-placearr.fill(value, start, end)
Imutáveis modernos (ES2023)
arr.toSorted((a, b) => ...) // novo array ordenadoarr.toReversed() // novo array invertidoarr.toSpliced(start, deleteCount, ...items)arr.with(index, value) // novo array com index substituído
Iterator Helpers (ES2025)
Operações lazy em iterators, sem criar arrays intermediários:
const map = new Map();map.set('key', 'value');map.set(objetoComoChave, 'value2');map.get('key');map.has('key');map.delete('key');map.size;for (const [k, v] of map) { ... }
Por que Map e não Object:
Map aceita qualquer chave, Object aceita só string/symbol
Map preserva ordem de inserção
Map tem .size
Map é otimizado para adições/remoções
Set — coleção de valores únicos.
const set = new Set([1, 2, 3, 3, 4]); // {1, 2, 3, 4}set.add(5);set.has(3);set.delete(1);[...set] // converter para array
Set Methods (ES2025)
const a = new Set([1, 2, 3, 4]);const b = new Set([3, 4, 5, 6]);a.intersection(b); // Set {3, 4}a.union(b); // Set {1, 2, 3, 4, 5, 6}a.difference(b); // Set {1, 2}a.symmetricDifference(b);// Set {1, 2, 5, 6}a.isSubsetOf(b);a.isSupersetOf(b);a.isDisjointFrom(b);
WeakMap e WeakRef
WeakMap — map com chaves fracamente referenciadas:
const wm = new WeakMap();let obj = { id: 1 };wm.set(obj, 'metadata');obj = null; // { id: 1 } pode ser GC'd, entry sai do wm
Uso: armazenar metadata de objetos sem impedir GC.
WeakRef (ES2021):
const ref = new WeakRef(heavyObject);// heavyObject pode ser GC'dconst obj = ref.deref(); // retorna objeto ou undefined
function* counter() { let i = 0; while (true) yield i++;}const gen = counter();gen.next(); // {value: 0, done: false}gen.next(); // {value: 1, done: false}// Rangefunction* range(start, end) { for (let i = start; i < end; i++) yield i;}for (const n of range(1, 5)) console.log(n);
Promises e Async/Await
Promise — o modelo
Uma Promise representa um valor que ainda não existe, mas existirá no futuro (ou falhará).
async function sempre retorna Promise.await só funciona em async function (ou top-level em módulos ESM).
Paralelismo — Promise.all, allSettled, race, any
// Rejeita se qualquer uma falharconst [users, orders, products] = await Promise.all([ fetchUsers(), fetchOrders(), fetchProducts()]);// Não rejeita — array com {status, value|reason}const results = await Promise.allSettled([p1, p2, p3]);// Primeira a fulfill OU rejectconst first = await Promise.race([p1, p2, p3]);// Primeira a fulfill (ignora rejects)const any = await Promise.any([p1, p2, p3]); // ES2021
// Em módulos ESM, await funciona no nível do arquivoimport { db } from './db.mjs';await db.connect();export const ready = true;
Iterators e AsyncIterators
Iterator protocol
class Range { constructor(start, end) { this.start = start; this.end = end; } *[Symbol.iterator]() { for (let i = this.start; i < this.end; i++) yield i; }}for (const n of new Range(1, 5)) console.log(n); // 1, 2, 3, 4
AsyncIterators — pagination, streaming
async function* fetchPages() { let page = 1; while (true) { const data = await fetch(`/api?page=${page}`).then(r => r.json()); if (data.length === 0) break; yield data; page++; }}for await (const page of fetchPages()) { console.log(page);}
Modules (ESM)
ESM (padrão moderno)
// math.mjsexport function sum(a, b) { return a + b; }export function mul(a, b) { return a * b; }export default function identity(x) { return x; }// main.mjsimport identity, { sum, mul } from './math.mjs';import * as math from './math.mjs';import { sum as add } from './math.mjs';
Características:
Static imports (tree shaking)
Strict mode sempre ligado
this é undefined no top level
Async — suporta top-level await
Live bindings
Dynamic import
// Carregamento sob demandaasync function loadLazy() { const { default: heavyLib } = await import('./heavy.mjs'); heavyLib.doStuff();}
CJS vs ESM
CJS
ESM
Sintaxe
require / module.exports
import / export
Loading
Síncrono
Assíncrono
Tree shaking
❌
✅
Top-level await
❌
✅
Strict mode
Opcional
Sempre
Em Node moderno (22+): use ESM. Node 22.18+ suporta TypeScript nativamente.
Import Attributes (ES2025)
import data from './data.json' with { type: 'json' };import styles from './styles.css' with { type: 'css' };
Substitui Date. API moderna inspirada em Java Time.
// Instante no tempo (UTC)const now = Temporal.Now.instant();// Data sem timezoneconst date = Temporal.PlainDate.from('2026-04-11');const date2 = date.add({ days: 30 });// Data + hora com timezoneconst zoned = Temporal.Now.zonedDateTimeISO('America/Sao_Paulo');// Duraçãoconst dur = Temporal.Duration.from({ hours: 2, minutes: 30 });// Formataçãoconst formatted = zoned.toLocaleString('pt-BR', { dateStyle: 'long' });
Até Temporal ser suportado universalmente, use date-fns ou dayjs (evite moment).
Explicit Resource Management (ES2026)
using e DisposableStack — try-with-resources em JavaScript.
// Anteslet file;try { file = openFile('data.txt');} finally { file?.close();}// Com using (ES2026){ using file = openFile('data.txt'); // usar file} // file.Symbol.dispose() chamado automaticamente// Async{ await using db = connect();} // db.Symbol.asyncDispose() chamado// Implementar disposableclass Connection { [Symbol.dispose]() { this.close(); }}
Memory management
Garbage Collection
JavaScript tem automatic GC baseado em reachability. Um objeto é coletado quando não há mais referências vivas.
V8 específicamente:
Young generation (new space) — objetos novos, coleção rápida frequente
Old generation — objetos que sobreviveram, coleção menos frequente
Large object space — objetos grandes
Memory leaks comuns
1. Global variables acidentais
function foo() { leak = 'bar'; // sem const/let/var — vira global}
2. Timers não limpos
const interval = setInterval(() => { ... }, 1000);// Esqueceu clearInterval — nunca é GC'd
3. Event listeners não removidos
element.addEventListener('click', handler);// removeu o element do DOM, mas handler ainda tem referência
Não precisa mais de tsx ou ts-node. Em Node 24+, é estável.
Bun
Package manager 10x mais rápido que npm
Runtime JS/TS completo
Bundler e test runner built-in
Em 2026, adquirido pela Anthropic
Deno
Runtime pensado em segurança (permissions explícitas)
TypeScript first-class
Deno 2 — compatibilidade com Node via deno install npm:
Armadilhas comuns
== vs === — sempre use ===
typeof null === 'object' — bug histórico
this perdido em callbacks — use arrow ou bind
Mutação via spread — spread é shallow
Floats para dinheiro — 0.1 + 0.2 !== 0.3
forEach com async — não espera. Use for…of + await ou Promise.all.
Unhandled promise rejections — sempre capture
Memory leaks com closures — cuidado com referências
JSON.parse(JSON.stringify()) para clone — perde Date, Map, funções. Use structuredClone.
Array(5).fill([]) compartilha referência — use Array.from({length: 5}, () => [])
Hoisting de var — prefira let/const
parseInt sem radix — parseInt("08", 10)
Number.MAX_SAFE_INTEGER = 2^53 - 1 — acima, use BigInt
Comparar arrays com === — compara referência
Nullish vs falsy — || pega falsy; ?? só null/undefined
await em forEach — não espera
Loop infinito via microtasks — bloqueia event loop
Na prática (da minha experiência)
Patterns que padronizei:
1. TypeScript estrito desde o dia 1. JavaScript puro em projeto novo é negligência. TS strict: true, noUncheckedIndexedAccess. Detalhes em TypeScript.
2. ESM em todo lugar."type": "module" no package.json. Node 22+. Top-level await, tree shaking, código mais limpo.
3. structuredClone em vez de JSON.parse(JSON.stringify(...)). Clone real, incluindo Date, Map, Set, circular references.
4. Promise.all + map em vez de loop. Paralelize quando possível. Se precisar de limite, use p-limit.
5. AbortController em toda chamada externa. Timeout, cancelamento, cleanup em React useEffect.
6. ?? em vez de || para defaults.|| pega 0, "", false como “falsy”.
7. Map e Set em vez de objetos/arrays quando a semântica é dicionário/conjunto.
8. for...of em vez de forEach quando preciso de await, break, ou continue.
9. Date → date-fns ou dayjs (e Temporal quando disponível).
Incidente memorável — race condition com fetch:
Componente React fazendo fetch no useEffect tinha race condition sob trocas rápidas. Usuário digitava “abc” na busca → dispara 3 fetches → resposta de “ab” chegava depois de “abc” → estado errado. Solução: AbortController no cleanup do effect.
Singleton global retinha referências a componentes React antigos via event listener não removido. Heap subia 2-3 MB por navegação. Descobri via Chrome DevTools Memory → heap snapshot comparison. Solução: WeakRef ou garantir removeEventListener no cleanup.
A lição principal: JavaScript moderno é poderoso mas abundante em armadilhas. Use TypeScript para tipos, ESLint strict para cultura, e tenha reflexos automáticos: ===, ??, Promise.all, cleanup, imutabilidade.
How to explain in English
“JavaScript is multi-paradigm — imperative, object-oriented with prototypes, and functional. Understanding it deeply means understanding three things: the event loop, closures, and the prototype chain.
The event loop makes JavaScript ‘single-threaded but concurrent’. A call stack executes code synchronously. When the stack is empty, the event loop picks the next macrotask — a setTimeout callback, an I/O completion — and runs it. Between macrotasks, all pending microtasks are drained, which is why Promise.then always runs before setTimeout(..., 0).
Closures are functions that capture their lexical scope. Every function in JavaScript is a closure, used for data privacy, memoization, and event handlers with state. The classic gotcha is var in loops — all closures share the same binding. let fixes this.
Prototypes are the real inheritance model. class is just syntactic sugar. Every object has an internal link to another object, and property lookups walk up the chain.
For async, I use async/await for linear code, Promise.all to parallelize independent operations, and AbortController for cancellation. I never use forEach with async — I use for...of with await for sequential, or Promise.all(arr.map(...)) for parallel.
Modern JavaScript (ES2020+) is concise and safe with optional chaining, nullish coalescing, destructuring, and spread. ES2025 brought Iterator Helpers and Set methods. ES2026 is bringing the Temporal API — finally a proper date/time replacement — and Explicit Resource Management with using, similar to Java’s try-with-resources.
For memory, the runtime handles GC, but I watch for common leaks: unremoved event listeners, uncleared timers, closures retaining large objects. Chrome DevTools Memory panel is my first tool for diagnosis.”
Frases úteis em entrevista
“JavaScript is single-threaded but concurrent through the event loop.”
“Microtasks always drain before the next macrotask.”
“Every function is a closure — it captures its lexical scope.”
“class is syntactic sugar over prototype-based inheritance.”
“Arrow functions don’t have their own this.”
“I never use ==, only ===. And ?? for defaults, not ||.”
“For parallelism, I use Promise.all. For cancellation, AbortController.”
“structuredClone is my default for deep cloning.”
“Avoid forEach with async — it doesn’t wait.”
“Temporal API in ES2026 finally replaces the broken Date.”