Fundamentos Avançados de JavaScript: Guia Completo para Entrevistas Técnicas

Tipos de Dados e Suas Características Fundamentais

O JavaScript possui oito categorias de dados fundamentais: undefined, null, number, string, boolean, Symbol, BigInt e Object. Estes se dividem em dois grupos distintos: primitivos e de referência.

Os tipos primitivos armazenam seus valores diretamente na pilha de execução, ocupando espaço reduzido e com tamanho fixo. Já os objetos, por serem estruturas complexas de tamanho variável, residem na memória heap, enquanto a pilha mantém apenas um ponteiro para seu endereço real.

Na pilha, os dados seguem o princípio LIFO (Last In, First Out), enquanto a heap opera como uma fila de prioridade. Do ponto de vista do sistema operacional, a pilha gerencia automaticamente memória para variáveis locais e parâmetros de função, ao contrário da heap que requer gerenciamento manual, embora o coletor de lixo do JavaScript libere memória não utilizada automaticamente.

Mecanismos de Verificação de Tipos

Existem quatro abordagens principais para identificar tipos em JavaScript:

  • typeof - Retorna uma string representando o tipo, porém classifica arrays, objetos e null como 'object'
  • instanceof - Verifica se um objeto possui em sua cadeia de protótipos a propriedade prototype de uma função construtora
  • constructor.name - Acessa o nome da função construtora, mas falha para null e undefined
  • Object.prototype.toString.call() - Método robusto que retorna [object Tipo] para qualquer valor

Identificação Específica de Arrays

Para determinar se um valor é um array, utilize:

// Método mais confiável
Array.isArray([1, 2, 3]) // true

// Verificação via protótipo
Object.prototype.toString.call([]) === '[object Array]'

// Comparação direta de protótipo
[].__proto__ === Array.prototype

// Utilizando isPrototypeOf
Array.prototype.isPrototypeOf([])

Distinção Crucial: null vs undefined

Ambos representam ausência de valor, porém em contextos diferentes. undefined indica que uma variável foi declarada mas não inicializada, enquanto null é uma atribuição explícita representando intencionalmente "nenhum objeto".

Curiosamente, typeof null retorna 'object' devido à implementação inicial do JavaScript, onde valores eram representados por um tipo de tag e um valor. null usava o ponteiro zero, que coincidentemente era a tag para objetos.

Em comparações, undefined == null retorna true (coerção de igualdade), mas undefined === null retorna false (tipos distintos).

Implementação Personalizada do Operador instanceof

function verificarInstancia(objeto, construtor) {
  let prototipoAtual = Object.getPrototypeOf(objeto);
  const prototipoAlvo = construtor.prototype;
  
  while (prototipoAtual !== null) {
    if (prototipoAtual === prototipoAlvo) {
      return true;
    }
    prototipoAtual = Object.getPrototypeOf(prototipoAtual);
  }
  
  return false;
}

// Teste
function Animal() {}
const cachorro = new Animal();
console.log(verificarInstancia(cachorro, Animal)); // true
console.log(verificarInstancia(cachorro, Array)); // false

Obtenção Segura do Valor undefined

Como undefined não é uma palavra reservada e pode ser sobrescrito, utilize o operador void para garantir o valor genuíno:

const valorIndefinido = void 0; // Sempre retorna undefined real

Características do Valor NaN

NaN (Not-a-Number) é do tipo number, resultando de operações matemáticas inválidas. Sua característica única é que NaN !== NaN retorna true, exigindo Number.isNaN() para verificação confiável.

Diferença Entre isNaN() e Number.isNaN()

isNaN() força a conversão do argumento para número antes da verificação, causando falsos positivos. Number.isNaN() primeiro confirma se o valor é do tipo número, então verifica se é NaN, sendo mais rigoroso.

Regras de Coerção do Operador de Igualdade Solta (==)

Quando tipos divergem, o JavaScript aplica estas transformações:

  1. Se um operando é null e o outro undefined, retorna true
  2. Comparando número e string, a string converte para número
  3. Booleano comparado com qualquer tipo converte para número (true → 1, false → 0)
  4. Objeto comparado com primitivo converte o objeto para valor primitivo via valueOf() ou toString()

Conversões para String

Valores convertidos para string resultam em:

  • null'null'
  • undefined'undefined'
  • true/false'true'/'false'
  • Números convertidos diretamente
  • Symbols apenas via conversão explícita
  • Objetos sem toString() personalizado → '[object Object]'

Conversões para Número

Transformações para tipo numérico:

  • undefinedNaN
  • null0
  • true/false1/0
  • String vazia → 0, string inválida → NaN
  • Arrays vazias → 0, arrays com um elemento numérico → esse número
  • Objetos → NaN

Conversões para Booleano

Valores avaliados como false: undefined, null, NaN, 0, -0, ''. Todos os outros valores, incluindo objetos vazios e arrays vazios, são true.

Comportamento dos Operadores Lógicos || e &&

Ambos operam por curto-circuito, retornando o operando que determina o resultado:

// || retorna o primeiro valor truthy
const resultado1 = '' || 'padrão'; // 'padrão'

// && retorna o primeiro valor falsy
const resultado2 = 'valor' && null && 'outro'; // null

Comparação Entre Object.is(), === e ==

=== compara igualdade estrita sem coerção. == permite coerção de tipos. Object.is() comporta-se como ===, mas trata +0 e -0 como diferentes e considera NaN igual a NaN.

Tipos de Envoltório (Wrapper Types)

Primitivos não possuem métodos, mas o JavaScript automaticamente os "envolve" em objetos temporários quando acessamos propriedades:

const texto = 'olá';
console.log(texto.toUpperCase()); // 'OLÁ' (temporariamente envolvido em String object)

// Conversão explícita
const numObj = Object(42);
const numPrimitivo = numObj.valueOf();

Mecanismos de Conversão Implícita

Quando operações exigem tipos específicos, o JavaScript segue este algoritmo:

  1. Para objetos Date: tenta toString(), depois valueOf()
  2. Para outros objetos: tenta valueOf(), depois toString()
  3. Operador + com string converte todos para string
  4. Operadores aritméticos (-, *, /) convertem para número
  5. Operador !! converte para booleano
  6. Comparações < e > entre strings usam ordem alfabética, outros casos convertem para número

Quando o Operador + Realiza Concatenação?

Quando qualquer operando é string ou após conversão de objeto resulta em string:

console.log(1 + '2'); // '12'
console.log({}.toString() + 1); // '[object Object]1'

Proposta do BigInt

Number.MAX_SAFE_INTEGER limita precisão acima de 2^53-1. BigInt resolve isso, representando inteiros arbitrariamente grandes através do sufixo n:

const grande = 9007199254740993n;
console.log(grande + 1n); // 9007199254740994n

Object.assign vs Operador Spread: Cópia Superficial

Ambos realizam cópia rasa (shallow copy):

const original = { a: 1, b: { c: 2 } };
const copia1 = Object.assign({}, original);
const copia2 = { ...original };

copia1.b.c = 3;
console.log(original.b.c); // 3 (ambas referenciam o mesmo objeto interno)

Object.assign() copia propriedades enumeráveis de múltiplas fontes. O spread copia elementos iteráveis, ignorando propriedades herdadas.

Comparação Detalhada: var, let e const

Característica var let const
Hoisting Sim, com inicialização undefined Sim, mas sem inicialização (Zona Temporal Morta) Sim, mas sem inicialização (Zona Temporal Morta)
Escopo Função ou global Bloco Bloco
Redeclaração Permitida Proibida Proibida
Valor inicial Opcional Opcional Obrigatório
Reatribuição Permitida Permitida Proibida
Propriedade window Sim Não Não

Mutabilidade de Objetos Const

const protege a referência, não o valor:

const usuario = { nome: 'Ana' };
usuario.nome = 'Maria'; // Permitido
usuario.idade = 25; // Permitido

// Proibido:
// usuario = { nome: 'João' }; // TypeError

Arrow Functions como Construtores

Tentar usar new em uma arrow function lança TypeError, pois elas não possuem prototype, this léxico fixo e não podem ser usadas como construtoras.

Diferenças Fundamentais: Arrow vs Function Regular

  • Sintaxe concisa: const soma = (a, b) => a + b;
  • this léxico, não vinculável via call/apply/bind
  • Sem objeto arguments (use rest parameters)
  • Sem prototype
  • Não pode ser usada como construtora
  • Não pode usar yield

Contexto de this em Arrow Functions

O this é capturado no momento da definição, herdado do escopo envolvente:

const objeto = {
  valor: 42,
  regular: function() { console.log(this.valor); },
  arrow: () => { console.log(this.valor); }
};

objeto.regular(); // 42
objeto.arrow(); // undefined (this é do escopo global)

Cenários de Uso do Operador Spread

Para objetos: clonagem rasa, mesclagem, sobrescrita de propriedades.

Para arrays: clonagem, concatenação, conversão de iteráveis.

Para funções: parâmetros rest.

// Mesclagem de objetos
const config = { ...configPadrao, ...configUsuario };

// Conversão de NodeList para array
const elementos = [...document.querySelectorAll('div')];

// Parâmetros rest
function somar(...numeros) { return numeros.reduce((a, b) => a + b); }

Funcionalidades do Proxy

Proxy intercepta operações básicas em objetos:

const alvo = { nome: 'Teste' };
const manipulador = {
  get(objeto, propriedade) {
    console.log(`Acessando ${propriedade}`);
    return objeto[propriedade];
  },
  set(objeto, propriedade, valor) {
    console.log(`Definindo ${propriedade} = ${valor}`);
    objeto[propriedade] = valor;
    return true;
  }
};

const proxy = new Proxy(alvo, manipulador);
proxy.nome; // "Acessando nome"
proxy.idade = 30; // "Definindo idade = 30"

Desestruturação de Objetos e Arrays

Arrays desestruturam por posição:

const [primeiro, segundo, , quarto] = [10, 20, 30, 40];
// primeiro = 10, segundo = 20, quarto = 40

Objetos desestruturam por nome:

const { idade, nome } = { nome: 'Carlos', idade: 25 };
// nome = 'Carlos', idade = 25 (ordem irrelevante)

Extraindo Propriedades Aninhadas

Para acessar propriedades profundas:

const dados = { usuario: { perfil: { email: 'test@ex.com' } } };
const { usuario: { perfil: { email } } } = dados;

// Com alias
const { usuario: { perfil: { email: contato } } } = dados;

Parâmetros Rest

Captura argumentos restantes em um array:

function logar(nivel, ...mensagens) {
  console.log(`[${nivel}]`, ...mensagens);
}
logar('INFO', 'Sistema', 'iniciado'); // [INFO] Sistema iniciado

Templates Literais e Processamento de Strings

Usando crases para strings multilinha e interpolação:

const nome = 'Maria';
const mensagem = `
  Olá ${nome}!
  Idade: ${20 + 5} anos
`;

Implementação do Operador new

function criarInstancia(construtor, ...parametros) {
  const instancia = Object.create(construtor.prototype);
  const resultado = construtor.apply(instancia, parametros);
  
  return (resultado !== null && (typeof resultado === 'object' || typeof resultado === 'function')) 
    ? resultado 
    : instancia;
}

// Uso
function Produto(nome, preco) {
  this.nome = nome;
  this.preco = preco;
}
const item = criarInstancia(Produto, 'Notebook', 3500);

Map vs Object: Escolha a Estrutura Correta

Aspecto Map Object
Chaves Qualquer tipo (objetos, funções) Apenas strings ou symbols
Ordem Preserva inserção Não garantida
Tamanho Propriedade size Manual via Object.keys()
Iteração Direta (iterable) Requer for...in ou métodos
Performance Melhor para adições/remoções frequentes Otizado para leitura

Map vs WeakMap

WeakMap aceita apenas objetos como chaves, mantém referências fracas (permitindo garbage collection) e não é enumerável. Map mantém referências fortes e preserva ordem de inserção.

Objetos Nativos do JavaScript

Objetos globais pré-definidos: Object, Function, Array, String, Number, Boolean, Date, RegExp, Math, JSON, Promise, Proxy, Reflect, Map, Set, WeakMap, WeakSet, Symbol, BigInt.

Expressões Regulares Comuns

// Cor hexadecimal
const regexCor = /^#([0-9A-F]{6}|[0-9A-F]{3})$/i;

// Data YYYY-MM-DD
const regexData = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;

// Email simples
const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

JSON: Formatos e Limitações

JSON não suporta: undefined, Symbol, Function, RegExp, Map, Set, WeakMap, WeakSet, referências circulares.

const obj = { a: 1, b: undefined, c: function() {} };
console.log(JSON.stringify(obj)); // {"a":1} (propriedades inválidas omitidas)

Técnicas de Carregamento Tardio de Scripts

  • Posicione <script> no final do <body>
  • Use defer para carregar em paralelo e executar após parsing
  • Use async para carregar e executar assim que disponível (pode bloquear parsing)
  • Crie dinamicamente via JavaScript: const script = document.createElement('script');
  • Utilize Intersection Observer para carregar sob demanda

Objetos Tipo Array (Array-like)

Possuem propriedade length e índices numéricos, mas não métodos de Aray: arguments, NodeList, HTMLCollection.

Conversão de Array-like para Array

// Métodos modernos
const array1 = Array.from(arrayLike);
const array2 = [...arrayLike];

// Método clássico
const array3 = Array.prototype.slice.call(arrayLike);

Métodos Essenciais de Arrays

Método Propósito Modifica original?
map() Transforma elementos Não
filter() Filtra elementos Não
reduce() Reduz a um valor Não
forEach() Itera sem retorno Pode modificar
slice() Extrai subarray Não
splice() Adiciona/remove elementos Sim
find() Busca elemento Não
some()/every() Testa condições Não

Unicode e Codificações UTF

Unicode é o conjunto de caracteres; UTF-8, UTF-16, UTF-32 são formas de codificação. UTF-8 é variável (1-4 bytes), eficiente para ASCII. UTF-16 usa 2 ou 4 bytes, melhor para caracteres Asiáticos. UTF-32 usa 4 bytes fixos, mais simples mas menos eficiente.

Operadores Bitwise

// Verificar paridade
const ehPar = n => (n & 1) === 0;

// Trocar valores sem variável temporária
let x = 5, y = 10;
x ^= y; y ^= x; x ^= y; // x=10, y=5

// Múltiplo de 2 mais próximo
const arredondar = n => (n + (n >> 31)) & ~1;

Objeto Arguments e Iteração

arguments é array-like, sem métodos de Array:

function listar() {
  // Converter e iterar
  Array.from(arguments).forEach(item => console.log(item));
  
  // Ou usar call
  Array.prototype.forEach.call(arguments, item => console.log(item));
}

DOM vs BOM

DOM (Document Object Model) manipula conteúdo da página. BOM (Browser Object Model) fornece acesso ao navegador (window, navigator, location, history).

Codificação de URIs

  • encodeURI(): codifica URI completa, preserva caracteres como :, /, ?, #
  • encodeURIComponent(): codifica componentes, transformando todos os caracteres não alfanuméricos
  • escape(): obsoleto, não codifica @ e usa formato %uXXXX para Unicode

Implementação Básica de AJAX

function requisitar(url, dados) {
  return new Promise((resolver, rejeitar) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', url);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolver(JSON.parse(xhr.response));
      } else {
        rejeitar(xhr.statusText);
      }
    };
    xhr.onerror = () => rejeitar(xhr.statusText);
    xhr.send(JSON.stringify(dados));
  });
}

Mecanismo de Içamento (Hoisting)

Funções e declarações var são movidas para o topo de seu escopo durante a fase de compilação, permitindo uso antes da declaração. let e const também são içadas, mas permanecem em "Zona Temporal Morta" até a declaração.

Chamadas de Cauda (Tail Calls)

Uma chamada de função na última linha de outra função permite otimização: o engine pode reutilizar o frame de stack atual, economizando memória. Ativada apenas em modo estrito no ES6.

'use strict';
function fatorial(n, acumulador = 1) {
  if (n <= 1) return acumulador;
  return fatorial(n - 1, n * acumulador); // Tail call otimizada
}

ES Modules vs CommonJS

Característica ES Modules CommonJS
Sistema Static (aálise em tempo de compilação) Dynamic (runtime)
Sintaxe import/export require/module.exports
Valores Live bindings (somente leitura) Cópia por valor
Contexto Strict mode por padrão Non-strict
Plataforma Navegadores modernos Node.js

Operações Comuns com DOM

// Seleção
const elemento = document.querySelector('.classe');
const lista = document.querySelectorAll('div');

// Criação
const novo = document.createElement('p');
novo.textContent = 'Texto';

// Inserção
elemento.appendChild(novo);

// Remoção
elemento.remove();

// Clonagem
const clone = elemento.cloneNode(true); // deep clone

Modo Estrito (use strict)

Ativado por 'use strict'; no topo de arquivos ou funções:

  • Proíbe variáveis globais implícitas
  • Elimina with
  • this em funções não vinculadas é undefined
  • Proíbe propriedades duplicadas em objetos
  • Proíbe parâmetros duplicados

Verificação de Herança

function Pessoa() {}
const joao = new Pessoa();

// instanceof
console.log(joao instanceof Pessoa); // true

// Verificação do construtor
console.log(joao.constructor === Pessoa); // true

// Verificação de tipo primitivo
console.log(Object.prototype.toString.call(joao)); // [object Object]

Tipagem Forte vs Fraca

Linguagens fortemente tipadas exigem declaração explícita e não realizam coerção automática (ex: Java, C#). Linguagens fracamente tipadas (ex: JavaScript, Python) convertem tipos implicitamente quando necessário, oferecendo flexibilidade mas exigindo cuidado.

Linguagens Interpretadas vs Compiladas

Interpretadas (JavaScript, Python) são traduzidas e executadas linha por linha em runtime, oferecendo portabilidade mas menor performance. Compiladas (C++, Rust) são convertidas para código de máquina antes da execução, gerando binários específicos da plataforma com alta performance.

for...in vs for...of

for...in for...of
Itera sobre chaves (strings) Itera sobre valores
Inclui propriedades herdadas Apenas iteráveis próprios
Usado para objetos Usado para arrays, strings, Map, Set

Iterando Objetos com for...of

const obj = { a: 1, b: 2 };

// Chaves
for (const chave of Object.keys(obj)) {
  console.log(chave);
}

// Valores
for (const valor of Object.values(obj)) {
  console.log(valor);
}

// Pares [chave, valor]
for (const [chave, valor] of Object.entries(obj)) {
  console.log(chave, valor);
}

Comparação: AJAX, Fetch e Axios

AJAX: Baseado em XMLHttpRequest, verboso, callback-based.

Fetch: API moderna, baseada em Promises, nativa, mas requer tratamento manual de erros e não cancela requisições.

Axios: Biblioteca baseada em Promises, com interceptadores, timeout, cancelamento, e suporte universal (navegador e Node.js).

Métodos de Iteração de Arrays

  • map(): Transformação
  • filter(): Filtragem
  • reduce(): Redução
  • forEach(): Efeitos colaterais
  • some()/every(): Testes condicionais
  • find()/findIndex(): Busca
  • flatMap(): Mapeamento e achatamento

Diferença Entre forEach e map

forEach executa uma ação para cada elemento sem retorno. map transforma cada elemento e retorna um novo array do mesmo tamanho.

const nums = [1, 2, 3];

// forEach não retorna
const resultado1 = nums.forEach(x => x * 2); // undefined

// map retorna novo array
const resultado2 = nums.map(x => x * 2); // [2, 4, 6]

Protótipos e Cadeia de Protótipos

Cada função construtora tem uma propriedade prototype contendo métodos compartilhados. Instâncias possuem __proto__ apontando para este objeto. Ao acessar propriedades, o motor busca na instância, depois no protótipo, depois no protótipo do protótipo, formando a cadeia até null.

Modificação vs Sobrescrita de Protótipo

Modificação: Adiciona propriedades ao protótipo existente.

function Animal() {}
Animal.prototype.comer = function() { console.log('Comendo'); };

Sobrescrita: Substitui o protótipo completamente, quebrando constructor:

function Veiculo() {}
Veiculo.prototype = {
  constructor: Veiculo, // Corrigir manualmente
  mover() { console.log('Movendo'); }
};

Relações na Cadeia de Protótipos

function Exemplo() {}
const instancia = new Exemplo();

console.log(instancia.__proto__ === Exemplo.prototype); // true
console.log(instancia.__proto__.__proto__ === Object.prototype); // true
console.log(instancia.__proto__.__proto__.__proto__ === null); // true

Fim da Cadeia de Protótipos

A cadeia termina em null, acessível via Object.prototype.__proto__ ou Object.getPrototypeOf(Object.prototype).

Propriedades Próprias vs Herdadas

Use hasOwnProperty() para distinguir:

const obj = { proprio: 1 };
console.log(obj.hasOwnProperty('proprio')); // true
console.log(obj.hasOwnProperty('toString')); // false (herdado)

Conceito e Aplicações de Closures

Closures permitem funções internas acessarem variáveis de funções externas mesmo após a execução externa ter terminado.

function criarFabricaDeId() {
  let id = 0;
  return () => ++id;
}

const gerarId = criarFabricaDeId();
console.log(gerarId()); // 1
console.log(gerarId()); // 2

Usado para: dados privados, funções de fábrica, contadores, memoização.

Escopos e Cadeia de Escopos

Global: Acessível em todo o código. Função: Acessível apenas dentro da função. Bloco: Criado por let/const em {}.

A cadeia de escopos permite que funções internas acessem variáveis externas, formando uma hierarquia de busca do mais interno para o global.

Contexto de Execução

Criado em duas fases:

  1. Criação: Vincula this, cria ambiente léxico e variável
  2. Execução: Executa código, atribui valores

A pilha de contextos gerencia chamadas: global → funções → funções aninhadas, seguindo LIFO.

Comportamento do this

this é determinado pela chamada:

  • Função regular: quem a chamou
  • Método de objeto: o objeto
  • Construtor: nova instância
  • Arrow function: léxico (contexto de definição)
  • call/apply/bind: explicitamente definido

Diferença Entre call e apply

Ambos invocam função com this específico. call() recebe argumentos separados, apply() recebe array de argumentos.

function saudar(mensagem, pontuacao) {
  console.log(`${mensagem}, ${this.nome}${pontuacao}`);
}

const pessoa = { nome: 'João' };
saudar.call(pessoa, 'Olá', '!'); // Olá, João!
saudar.apply(pessoa, ['Oi', '?']); // Oi, João?

Implementação de call, apply e bind

// Implementação de call
Function.prototype.invocar = function(contexto, ...args) {
  const chave = Symbol('temp');
  contexto[chave] = this;
  const resultado = contexto[chave](...args);
  delete contexto[chave];
  return resultado;
};

// Implementação de apply
Function.prototype.aplicar = function(contexto, args) {
  if (!Array.isArray(args)) throw new TypeError('Args deve ser array');
  return this.invocar(contexto, ...args);
};

// Implementação de bind
Function.prototype.vincular = function(contexto, ...argsFixos) {
  const funcaoOriginal = this;
  return function(...args) {
    return funcaoOriginal.invocar(contexto, ...argsFixos, ...args);
  };
};

Abordagens de Programação Assíncrona

  1. Callbacks (callback hell)
  2. Promises (encadeamento .then)
  3. Async/await (sintaxe síncrona)
  4. Generators (function* e yield)

Comparação: setTimeout, Promise e async/await

setTimeout agenda execução futura. Promise representa valor futuro. async/await é sintaxe sugar sobre Promises, permitindo código assíncrono com aparência síncrona.

Fundamentos de Promises

Promise é objeto representando estado eventual de operação assíncrona. Estados: pendingfulfilled (resolve) ou rejected (reject). Uma vez resolvida, o estado é imutável.

const promessa = new Promise((resolver, rejeitar) => {
  setTimeout(() => resolver('Concluído'), 1000);
});

promessa
  .then(valor => console.log(valor))
  .catch(erro => console.error(erro))
  .finally(() => console.log('Sempre executa'));

Métodos Estáticos de Promise

  • Promise.all(): Espera todas resolvidas (rejeita na primeira falha)
  • Promise.race(): Resolve/rejeita na primeira a concluir
  • Promise.allSettled(): Espera todas independente de sucesso/fracasso
  • Promise.any(): Resolve na primeira bem-sucedida

Async/Await: Sintaxe Moderna

async function buscarDados() {
  try {
    const resposta = await fetch('/api/dados');
    const dados = await resposta.json();
    return dados;
  } catch (erro) {
    console.error('Falha:', erro);
  }
}

async retorna Promise. await pausa execução até Promise resolver.

Vantagens do Async/Await Sobre Promises

  • Código linear, mais legível
  • Tratamento de erros com try/catch familiar
  • Depuração mais intuitiva com stack traces completos
  • Menos aninhamento que encadeamento .then

Paralelismo vs Concorrência

Paralelismo: Execução simultânea real (múltiplos cores). Concorrência: Alternância rápida de tarefas (single-core), dando ilusão de simultaneidade.

Callback Hell e Soluções

Múltiplos callbacks aninhados criam código ilegível. Soluções: modularização, Promises, async/await.

// Callback hell
getData(a => {
  getMoreData(a, b => {
    getMoreData(b, c => {
      console.log(c);
    });
  });
});

// Solução com async/await
const resultado = await getMoreData(await getMoreData(await getData()));

Timers e Animações

setTimeout: Executa uma vez após atraso. setInterval: Executa repetidamente. requestAnimationFrame: Sincronizado com taxa de atualização do navegador, ideal para animações suaves.

Padrões de Criação de Objetos

// Factory
function criarUsuario(nome, idade) {
  return { nome, idade, saudar() { return `Olá, ${this.nome}`; } };
}

// Constructor
function Livro(titulo, autor) {
  this.titulo = titulo;
  this.autor = autor;
}
Livro.prototype.info = function() { return `${this.titulo} por ${this.autor}`; };

// Class (ES6)
class Carro {
  constructor(marca) { this.marca = marca; }
  acelerar() { console.log(`${this.marca} acelerando`); }
}

// Object.create
const prototipo = { saudar() { return 'Oi'; } };
const instancia = Object.create(prototipo);

Padrões de Herança

// Herança prototípica
function Animal(tipo) { this.tipo = tipo; }
function Cachorro(nome) { Animal.call(this, 'cão'); this.nome = nome; }
Cachorro.prototype = Object.create(Animal.prototype);
Cachorro.prototype.constructor = Cachorro;

// Herança com classes
class Animal {
  constructor(tipo) { this.tipo = tipo; }
}
class Cachorro extends Animal {
  constructor(nome) { super('cão'); this.nome = name; }
}

Coleta de Lixo (Garbage Collection)

Algoritmos principais:

  • Mark-and-Sweep: Marca objetos alcançáveis a partir de raízes, remove não marcados
  • Reference Counting: Conta referências; quando zero, coleta (vulnerável a ciclos)

Otimizações: reutilize objetos, limpe referências com null, evite criar funções em loops.

Causas Comuns de Vazamento de Memória

  • Variáveis globais acidentais
  • Timers não limpos (setInterval)
  • Referências DOM em cache após remoção de elementos
  • Closures capturando objetos grandes desnecessariamente
  • Event listeners não removidos

Event Loop e Microtasks/Macrotasks

JavaScript é single-threaded. O event loop processa:

  1. Executa todas as tarefas síncronas
  2. Processa todas as microtasks (Promise.then, queueMicrotask)
  3. Executa uma macrotask (setTimeout, setInterval, I/O)
  4. Repete

Microtasks têm prioridade sobre macrotasks.

Alias em Desestruturação

const pessoa = { primeiroNome: 'Ana', idade: 30 };

const { primeiroNome: nome, idade: anos } = pessoa;
// nome = 'Ana', anos = 30

Tags: javascript ecmascript-6 prototype-chain closures asynchronous-programming

Publicado em 9-7 07:40