5 Lacunas Críticas que Todo Dev JavaScript Ignora (e Como Cobrir Cada Uma)
## O Problema Real
A maioria dos devs JavaScript sabe fazer funcionar. Poucos sabem fazer funcionar **direito**.
Depois de revisar centenas de PRs em projetos open source e mentorar times, identifiquei cinco lacunas que se repetem. Não são bugs óbvios. São buracos silenciosos que explodem em produção.
Este post cobre cada um com código funcional. Sem teoria vazia.
## Lacuna 1: Event Loop e Microtasks Mal Compreendidos
Esse é o gap mais perigoso. Devs escrevem `async/await` sem entender a fila de execução.
Veja este código. Qual a ordem de saída?
```javascript
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
queueMicrotask(() => console.log("4"));
console.log("5");
A resposta correta é: 1, 5, 3, 4, 2.
Se você errou, essa lacuna está aberta no seu código. Microtasks (Promises, queueMicrotask) executam antes de macrotasks (setTimeout, setInterval). Isso afeta tudo: ordem de renderização, race conditions, estado inconsistente.
Como cobrir na prática
Crie um scheduler explícito quando a ordem importa:
class TaskScheduler {
#microtasks = [];
#macrotasks = [];
#isProcessing = false;
addMicrotask(fn, label = "unnamed") {
this.#microtasks.push({ fn, label });
this.#scheduleFlush();
}
addMacrotask(fn, label = "unnamed") {
this.#macrotasks.push({ fn, label });
this.#scheduleFlush();
}
#scheduleFlush() {
if (this.#isProcessing) return;
this.#isProcessing = true;
queueMicrotask(() => {
this.#flush();
this.#isProcessing = false;
});
}
#flush() {
// Microtasks primeiro, respeitando a spec
while (this.#microtasks.length > 0) {
const task = this.#microtasks.shift();
console.log(`[micro] ${task.label}`);
task.fn();
}
// Uma macrotask por ciclo, como o browser faz
if (this.#macrotasks.length > 0) {
const task = this.#macrotasks.shift();
console.log(`[macro] ${task.label}`);
setTimeout(() => {
task.fn();
if (this.#macrotasks.length > 0) this.#scheduleFlush();
}, 0);
}
}
}
// Uso
const scheduler = new TaskScheduler();
scheduler.addMacrotask(() => fetch("/api/logs"), "send-logs");
scheduler.addMicrotask(() => updateUI(), "ui-update");
Esse padrão torna a ordem de execução visível e testável. Sem surpresas.
Lacuna 2: Memory Leaks em Closures e Event Listeners
JavaScript tem garbage collector. Mas ele não é mágico.
O leak mais comum? Closures que capturam referências gigantes sem necessidade.
// ❌ Leak: handler mantém referência ao array inteiro
function setupSearch(hugeDataset) {
const searchInput = document.getElementById("search");
searchInput.addEventListener("input", (e) => {
// hugeDataset (10MB) nunca será coletado enquanto
// o listener existir
const results = hugeDataset.filter((item) =>
item.name.includes(e.target.value)
);
renderResults(results);
});
}
O hugeDataset fica preso na closure. Mesmo que você nunca mais precise dele inteiro.
A correção real
// ✅ Sem leak: WeakRef + cleanup explícito
function setupSearch(hugeDataset) {
const searchInput = document.getElementById("search");
// Pré-processa: cria índice leve
const searchIndex = new Map();
for (const item of hugeDataset) {
const key = item.name.toLowerCase();
if (!searchIndex.has(key)) {
searchIndex.set(key, []);
}
searchIndex.get(key).push(item.id);
}
// hugeDataset pode ser coletado agora
const controller = new AbortController();
searchInput.addEventListener(
"input",
(e) => {
const query = e.target.value.toLowerCase();
const matchedIds = [];
for (const [key, ids] of searchIndex) {
if (key.includes(query)) matchedIds.push(...ids);
}
renderResults(matchedIds);
},
{ signal: controller.signal }
);
// Cleanup quando necessário
re
---
Leia o artigo completo em [https://vivodecodigo.com.br/backend/lacunas-criticas-javascript-opensource-webdev](https://vivodecodigo.com.br/backend/lacunas-criticas-javascript-opensource-webdev)