A compreensão dos conceitos fundamentais do JavaScript é essencial para escrever código eficiente e confiável. Abaixo, exploramos aspectos cruciais como o mecanismo de execução assíncrona, gerenciamento de memória, sistema de herança e recursos contemporâneos.
- Ciclo de Eventos (Event Loop)
O ciclo de eventos é o coração do tratamento assíncrono no JavaScript, permitindo operações não bloqueantes em uma linguagem de thread única. Ele gerencia filas de tarefas de diferentes prioridades.
Macrotarefas vs. Microtarefas
- Macrotarefas: Tarefas originadas pelo ambiente (navegador ou Node.js). Exemplos incluem:
setTimeoutesetIntervalsetImmediate(exclusivo do Node.js)- Operações de I/O (leitura de arquivos, requisições de rede)
- Renderização da interface (navegador)
- O código principal do script
- Microtarefas: Tarefas iniciadas pelo próprio JavaScript, frequentemente ligadas a promessas. Exemplos:
Promise.then(),Promise.catch(),Promise.finally()process.nextTick(Node.js, com prioridade máxima)MutationObserver(navegador)
Regras de Execução no Navegador
- Execute uma macrotarefa (geralmente a mais antiga na fila), como o script global.
- Durante a execução, se encontrar microtarefas, coloque-as na fila de microtareafs; se encontrar macrotarefas, coloque-as na fila de macrotarefas.
- Após a conclusão da macrotarefa atual, processe imediatamente todas as microtarefas na fila.
- Realize a renderização da interface, se necessário.
- Retire a próxima macrotarefa da fila e comece um novo ciclo.
Princípio fundamental: uma macrotarefa → todas as microtarefas → (renderização) → próxima macrotarefa.
Diferenças entre Node.js e Navegador
O Node.js baseia seu ciclo de eventos na biblioteca libuv, com fases mais complexas.
Fases simplificadas no Node.js:
- Timers: Executa callbacks de
setTimeoutesetInterval. - Pending callbacks: Processa callbacks de I/O atrasados.
- Idle, Prepare: Uso interno.
- Poll: Obtém novos eventos de I/O e executa seus callbacks (pode bloquear se necessário).
- Check: Executa callbacks de
setImmediate. - Close callbacks: Processa eventos de encerramento (ex.:
socket.on('close', ...)).
process.nextTick() é executado entre as fases, com prioridade superior às microtarefas comuns.
Exemplo de Execução Assíncrona Complexa
console.log('Início da execução');
setTimeout(() => {
console.log('Execução do setTimeout');
Promise.resolve().then(() => {
console.log('Promessa dentro do setTimeout');
});
}, 0);
Promise.resolve()
.then(() => {
console.log('Primeira promessa');
})
.then(() => {
console.log('Segunda promessa');
});
console.log('Fim da execução');
// Saída esperada:
// Início da execução
// Fim da execução
// Primeira promessa
// Segunda promessa
// Execução do setTimeout
// Promessa dentro do setTimeout
- Gerenciamento de Memória e Coleta de Lixo
O JavaScript aloca e libera memória automaticamente por meio da coleta de lixo (Garbage Collection).
Mecanismo de Coleta de Lixo no V8
O V8 emprega uma coleta de lixo baseada em gerações, dividindo a memória em:
- Espaço Novo (New Space):
- Armazena objetos de vida curta (ex.: variáveis locais).
- Utiliza o algoritmo Scavenge (uma cópia eficiente), que move objetos sobreviventes de
From-SpaceparaTo-Space. - Objetos que sobrevivem múltiplas coletas são promovidos ao espaço antigo.
- Espaço Antigo (Old Space):
- Armazena objetos de vida longa ou grandes (ex.: variáveis globais, closures).
- Usa algoritmos como Marcação-Limpeza (Mark-Sweep) e Marcação-Compactação (Mark-Compact).
- Marcação-Limpeza identifica objetos alcançáveis a partir de raízes e remove os não marcados; Marcação-Compactação reorganiza a memória para reduzir fragmentação.
Referência contagem é uma técnica antiga e ineficaz para ciclos de referência.
Cenários Comuns de Vazamento de Memória
- Variáveis globais acidentais: Em modo não estrito, atribuir a variáveis não declaradas cria globais.
- Timers ou callbacks esquecidos: Callbacks que mantêm referências a grandes objetos podem impedir a coleta.
- Referências a nós do DOM removidos: Manter referências a elementos DOM já removidos do documento.
- Closures: Closures podem reter referências a variáveis de escopo externo desnecessárias.
Depuração com Chrome DevTools
- Painel Performance: Grava o desempenho para verificar se a memória do heap cresce continuamente.
- Painel Memory: Use Heap Snapshots para comparar alocações ao longo do tempo, ou Allocation instrumentation para rastrear distribuições de memória em tempo real.
- Protótipos e Herança
JavaScript utiliza cadeias de protótipos para implementar herança.
Cadeia de Protótipos
- Cada função tem uma propriedade
prototype(objeto protótipo). - Cada objeto tem uma propriedade
__proto__(referência ao protótipo de seu construtor). - Ao acessar uma propriedade, se não existir no objeto, a busca segue a cadeia
__proto__até encontrar ou atingirnull.
function Pai() {
this.nome = 'Pai';
}
Pai.prototype.dizerNome = function() {
console.log(this.nome);
};
function Filho() {}
Filho.prototype = new Pai(); // ou Filho.prototype = Object.create(Pai.prototype)
Filho.prototype.constructor = Filho;
const filho = new Filho();
filho.dizerNome(); // 'Pai'
Sintaxe de Classe
A palavra-chave class no ES6 é uma simplificação sintática para herança baseada em protótipos.
class Pai {
constructor() {
this.nome = 'Pai';
}
dizerNome() {
console.log(this.nome);
}
}
class Filho extends Pai {
constructor() {
super();
}
}
// A estrutura do protótipo é semelhante ao exemplo anterior.
Padrões de Herança e Trade-offs
-
Herança via protótipo: Simples, mas propriedades compartilhadas podem causar efeitos colaterais; sem passagem de parâmetros.
-
Herança via construtor: Resolve compartilhamento e permite parâmetros, mas métodos não são reutilizáveis.
-
Herança combinada: Combina as duas abordagens acima, amplamente usada, mas invoca o construtor pai duas vezes.
-
Herança prototípica (com
Object.create()): Cria objetos sem construtor, mas propriedades são compartilhadas. -
Herança parasitária combinada: Abordagem otimizada que invoca o construtor pai uma vez e evita propriedades desnecessárias no protótipo do filho. É a base da implementação de
extends. -
Recursos Modernos do ES6+
Promise
Promessas gerenciam operações assíncronas, evitando "callback hell". Estados: pending, fulfilled, rejected. APIs como .then(), .catch(), Promise.all().
Async/Await
Sintaxe que simplifica código assíncrono, baseada em promessas. Funções async retornam promessas; await pausa execução até que a promessa resolva.
Geradores (Generators)
Funções que podem pausar e retomar com function* e yield. Fundamentam a implementação do Async/Await.
function* sequencia() {
yield 'primeiro';
yield 'segundo';
}
const gen = sequencia();
gen.next(); // { value: 'primeiro', done: false }
Módulos (ES Module)
Solução de modularização nativa com export e import. Ético e estático, permitindo otimizações como Tree Shaking.
Funções Arrow
Sintaxe concisa; não possuem this próprio (herdam do escopo léxico); não têm arguments e não podem ser usadas como construtoras.
Desestruturação
Extrai valores de arrays ou objetos para variáveis.
// Array
const [x, y] = [10, 20];
// Objeto
const { id, valor } = { id: 1, valor: 'exemplo' };
// Em parâmetros de função
function processar({ chave, dados }) { /* ... */ }