- Formas de Carregamento Assíncrono de Scripts em JavaScript
Quando o navegador encontra uma tag <script> no HTML, ele pausa a análise e renderização da página para baixar e executar o script. Múltiplos scripts são baixados em paralelo, mas executados na ordem em que aparecem no documento.
Método com atributo defer
Ao adicionar defer à tag script:
- O download e a execução não bloqueiam a análise e renderização da página
- Múltiplos scripts defer são baixados em paralelo, mas executados sequencialmente
- A execução ocorre após a conclusão da análise do DOM, antes do evento DOMContentLoaded
Método com atributo async
Ao adicionar async à tag script:
- O download não bloqueia a análise, mas a execução bloqueia a renderização
- O primeiro script a ser totalmente baixado será executado imediatamente, sem respeitar a ordem do documento
- O impacto no evento DOMContentLoaded é mínimo, pois o tempo de execução geralmente é curto
Criação dinâmica de elementos script
Antes de existirem defer e async, a técnica consistia em criar dinamicamente elementos <script> e inseri-los no DOM após o carregamento completo da página usando window.onload.
- Diferenças entre var, let e const
- Elevação de declaração (hoisting):
varpossui hoisting;leteconstnão apresentam elevação de declaração - Re-declaração: Variáveis com
varpodem ser redeclaradas;leteconstnão permitem re-declaração - Escopo de bloco:
varnão possui escopo de bloco;leteconstpossuem escopo de bloco - Inicialização obrigatória:
constexige inicialização na declaração - Mutabilidade:
constnão permite reatribuição;vareletpermitem - Zona morta temporal (TDZ):
leteconstficam indisponíveis antes da declaração - Propriedade global:
vardeclara variáveis como propriedades dewindow;leteconstnão
console.log(valorX) // undefined
console.log(valorY) // ReferenceError
var valorX = 10;
let valorY = 20;
- Tipos de Dados em JavaScript
Tipos Primitivos
- String: Ao concatenar com qualquer outro tipo, o resultado é uma string
- Number: Utiliza representação de ponto flutuante de 64 bits.
NaNé do tipo number mas não representa um número válido.isNaN()verifica se o valor é NaN - Boolean: Pode assumir apenas
trueoufalse - Undefined: Indica variável declarada mas não inicializada
- Null: Representa ausência intencional de valor.
typeof nullretorna "object" - Symbol: Cria valores únicos, útil para evitar conflitos de propriedades em objetos
- BigInt: Permite representar números inteiros com precisão arbitrária além do limite de Number
Tipos de Referência (Objetos)
- Object, Array, Function, RegExp, Date
- Valores Retornados pelo typeof
O operador typeof pode retornar: "number", "string", "boolean", "undefined", "object", "function", "symbol", "bigint".
- Diferença entre null e undefined
nullrepresenta a ausência intencional de um objeto; convertido para número resulta em 0;typeofretorna "object"undefinedrepresenta uma variável não definida; convertido para número resulta emNaN;typeofretorna "undefined"null == undefinedétrue, masnull === undefinedéfalse
- Diferença entre == e ===
== (igualdade simples): Realiza conversão implícita de tipos antes da comparação. Strings são convertidas para números, booleanos para 0 ou 1, e objetos invocam valueOf() e toString().
1 == '1' // true
0 == false // true
[] == false // true
null == undefined // true
=== (igualdade estrita): Compara tanto tipo quanto valor. Se os tipos diferem, retorna false imediatamente.
0 === '0' // false
0 === false // false
null === undefined // false
- Diferença entre typeof e instanceof
typeofretorna uma string com o tipo do dadoinstanceofretorna um booleano indicando se o objeto é instância de um construtor específicotypeofnão diferencia tipos de objetos;instanceofpode verificar tipos específicos
- Implementação Manual do instanceof
O operador instanceof verifica se o prototype do construtor está presente na cadeia de protótipos do objeto:
function verificarInstancia(objeto, construtor) {
if (objeto === null || typeof objeto !== 'object') {
return false;
}
if (typeof construtor !== 'function') {
return false;
}
let prototipoAtual = Object.getPrototypeOf(objeto);
while (prototipoAtual !== null) {
if (prototipoAtual === construtor.prototype) {
return true;
}
prototipoAtual = Object.getPrototypeOf(prototipoAtual);
}
return false;
}
- Diferença entre substring e substr
const texto = "hello, world!";
console.log(texto.substring(7, 12)); // "world"
console.log(texto.substr(7, 5)); // "world"
substring(inicio, fim) utiliza índices de início e fim. substr(inicio, comprimento) utiliza índice inicial e quantidade de caracteres.
- Diferença entre for...in e for...of
| Aspecto | for...in | for...of |
|---|---|---|
| Retorna | Chaves (keys) | Valores (values) |
| Adequado para | Objetos, arrays, strings | Iteráveis: Array, String, Map, Set, Generator |
| Propriedades do protótipo | Sim, percorre propriedades herdadas | Não, apenas propriedades próprias |
| Ordem garantida | Não | Sim |
Para iterar sobre objetos com for...of:
const pessoa = { nome: "Ana", idade: 30, cidade: "Lisboa" };
pessoa[Symbol.iterator] = function* () {
const chaves = Object.keys(this);
for (const chave of chaves) {
yield this[chave];
}
};
for (const valor of pessoa) {
console.log(valor); // "Ana", 30, "Lisboa"
}
- Compreensão do AJAX e Implementação
AJAX (Asynchronous JavaScript and XML) permite comunicação assíncrona com servidores. O objeto XMLHttpRequest possui 5 estados:
- 0 - Não inicializado
- 1 - Conexão estabelecida, requisição enviada
- 2 - Requisição recebida pelo servidor
- 3 - Processando resposta
- 4 - Resposta completa
- Comparação: AJAX, Axios e Fetch
- AJAX: Baseado em XMLHttpRequest, propenso a callback hell, código verboso
- Fetch: API nativa do navegador, baseada em Promises, suporta async/await, não envia cookies por padrão
- Axios: Biblioteca de terceiros, baseada em XMLHttpRequest, com interceptors, conversão automática de JSON e suporte a CSRF
- Otimização de Cauda (Tail Call)
Uma chamada de cauda ocorre quando uma função retorna diretamente o resultado de outra invocação de função como sua última operação:
// Chamada de cauda
function calcular(valor) {
return processar(valor);
}
// NÃO é chamada de cauda
function calcular(valor) {
const resultado = processar(valor);
return resultado;
}
O benefício principal é a economia de memória, pois o contexto de execução atual pode ser descartado quando a chamada de cauda é realizada.
- Cópia Rasa (Shallow Copy) e Cópia Profunda (Deep Copy)
Cópia Rasa
Copia apenas o primeiro nível de propriedades. Métodos disponíveis:
Object.assign({}, objetoOriginal)- Operador spread:
{ ...objetoOriginal }
function copiaRasa(origem) {
if (!origem || typeof origem !== 'object') {
return origem;
}
const copia = Array.isArray(origem) ? [] : {};
for (const propriedade of Object.keys(origem)) {
if (origem.hasOwnProperty(propriedade)) {
copia[propriedade] = origem[propriedade];
}
}
return copia;
}
Cópia Profunda
Copia todas as propriedades recursivamente, criando novas referências. A abordagem com JSON.parse(JSON.stringify(obj)) possui limitações: perde funções, undefined e Symbols.
- Funções Arrow vs Funções Tradicionais
- Arrow functions não podem ser usadas como construtoras (sem
new) - Não possuem
arguments - O
thisé herdado do contexto léxico call(),apply()ebind()não alteram othis- Não possuem
prototype
Cenários onde arrow functions não são recomendadas: métodos de objetos, protótipos, funções construtoras, callbacks com contexto dinâmico, hooks e métodos do Vue.js.
- Diferenças entre Set e Map
- Set: Coleção de valores únicos, útil para remover duplicatas de arrays
- Map: Coleção de pares chave-valor, aceita qualquer tipo como chave
- Diferenças entre Map e Object
- Tipos de chave: Map aceita qualquer tipo; Object aceita apenas strings e Symbols
- Ordem: Map preserva a ordem de inserção; Object não garante ordem
- Iteração: Map é diretamente iterável com for...of; Object requer métodos específicos
- Performance: Ambos oferecem operações rápidas, mas Map é mais eficiente para adições e remoções frequentes
- Compreensão de Promises
Uma Promise representa uma operação assíncrona futura e possui três estados:
- Pending: Estado inicial
- Fulfilled: Operação concluída com sucesso
- Rejected: Operação falhou
As transições de estado são irreversíveis. A construção recebe uma função com resolve e reject:
const promessa = new Promise((resolver, rejeitar) => {
const sucesso = true;
if (sucesso) {
resolver('Operação concluída');
} else {
rejeitar('Erro na operação');
}
});
Comportamento de then e catch: Quando executados sem erros, retornam uma Promise fulfilled. Se houver erro, retornam uma Promise rejected. Importante: catch sem erro retorna fulfilled, acionando o próximo then.
Métodos estáticos:
Promise.all()- Resolve quando todas as promises resolvem; rejeita imediatamente se qualquer uma falharPromise.race()- Resolve/rejeita com o primeiro resultadoPromise.allSettled()- Aguarda todas completarem, independentemente do resultado
- Cookie, localStorage e sessionStorage
| Aspecto | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| Capacidade | ~4KB | ~5MB | ~5MB |
| Enviado ao servidor | Sim, automaticamente | Não | Não |
| Persistência | Até expiração | Permanente | Até fechar aba/janela |
| Escopo | Por domínio | Por domínio | Por aba/janela |
- Processo de Renderização: da URL à Página
-
Resolução DNS: Converte domínio em endereço IP
-
Conexão TCP: Estabelece conexão via three-way handshake
-
Requisição HTTP: Navegador solicita recursos ao servidor
-
Análise do HTML: Constrói a árvore DOM
-
Análise do CSS: Constrói a árvore CSSOM
-
Render Tree: Combina DOM e CSSOM
-
Layout e Pintura: Calcula geometria e renderiza pixels na tela
-
Diferença entre window.onload e DOMContentLoaded
window.addEventListener('load', () => {
// Executa após TODOS os recursos carregarem (imagens, vídeos, etc.)
});
window.addEventListener('DOMContentLoaded', () => {
// Executa assim que o DOM estiver pronto, recursos multimídia podem não estar prontos
});
- Conversão de Tipos: Explícita e Implícita
Conversão explícita: Number(), parseInt(), parseFloat(), String(), Boolean(), toString().
Conversão implícita: Ocorre em construções condicionais, operações lógicas, comparação com == e concatenação com +.
- O Enigma de [10,20,30].map(parseInt)
const resultado = [10, 20, 30, 221].map((elemento, indice) =>
parseInt(elemento, indice)
);
console.log(resultado); // [10, NaN, NaN, 25]
map passa o elemento e o índice como argumentos. parseInt(10, 0) usa base 10 (resultado: 10). parseInt(20, 1) usa base inválida (NaN). parseInt(30, 2) usa base 2, onde '3' é inválido (NaN). parseInt(221, 3) usa base 3 (resultado: 25).
- Declaração vs Expressão de Função
// Declaração - possui hoisting
console.log(somar(5, 3)); // 8
function somar(a, b) {
return a + b;
}
// Expressão - sem hoisting
console.log(multiplicar(5, 3)); // ReferenceError
const multiplicar = function(a, b) {
return a * b;
};
- new Object() vs Object.create()
const obj1 = new Object({ x: 10, y: 20 });
const obj2 = { x: 10, y: 20 }; // Equivalente a new Object()
const obj3 = Object.create({ x: 10, y: 20 }); // Protótipo personalizado
const obj4 = Object.create(null); // Sem protótipo algum
- Escopo e Variáveis Livres
let contador;
for (contador = 1; contador <= 3; contador++) {
setTimeout(() => {
console.log(contador); // Imprime 4, 4, 4
}, 0);
}
Como var não tem escopo de bloco (ou a variável é compartilhada), o setTimeout captura a referência final de contador (valor 4 após o loop).
- Implementação Manual de trim com Compatibilidade
String.prototype.trimCustom = function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
};
const texto = ' Olá Mundo ';
console.log(texto.trimCustom()); // "Olá Mundo"
- Obtendo Parâmetros da URL
// Método tradicional
function obterParametro(nome) {
const pesquisa = location.search.substring(1);
const parametros = new URLSearchParams(pesquisa);
return parametros.get(nome);
}
// Usando URLSearchParams diretamente
function obterParametroModerno(nome) {
return new URLSearchParams(location.search).get(nome);
}
- requestAnimationFrame
Utilizado para animações complexas controladas por JavaScript. Diferente de setTimeout, o navegador controla automaticamente a taxa de quadros, pausa quando a aba não está visível e otimiza o processo de renderização.
- Por que 0.1 + 0.2 !== 0.3?
Computadores utilizam representação binária de ponto flutuante (IEEE 754). Tanto 0.1 quanto 0.2 possuem representações binárias infinitas (período 0011), resultando em arredondamentos. A soma resulta em 0.30000000000000004, que difere da representação binária de 0.3.
- HTMLCollection vs NodeList
- HTMLCollection: Coleção de Elementos (ex:
elemento.children,document.getElementsByTagName()) - NodeList: Coleção de Nodes, incluindo elementos, textos e comentários (ex:
document.querySelectorAll(),elemento.childNodes)
Ambos são "array-like". Conversão para array: Array.from(lista), [...lista], Array.prototype.slice.call(lista).
- Modo Estrito em JavaScript
Ativado com 'use strict' no início do arquivo ou função. Características:
- Variáveis globais devem ser declaradas explicitamente
- Uso de
withé proibido thisem funções invocadas sem contexto éundefined(nãowindow)- Parâmetros de função não podem ser duplicados
evalcria seu próprio escopo
- Requisição OPTIONS em CORS
O navegador envia automaticamente uma requisição OPTIONS (pre-flight) antes de requisições cross-origin complexas. Este mecanismo verifica se o servidor permite a operação solicitada, sem intervenção do desenvolvedor.
- Coleta de Lixo e Vazamento de Memória
O mecanismo de garbage collection (GC) libera automaticamente a memória ocupada por variáveis que não são mais referenciadas.
Algoritmos de Coleta
- Contagem de referências: Algoritmo antigo que mantém contagem de referências a cada objeto. Problema: não trata referências circulares
- Mark and Sweep: Algoritmo moderno utilizado pelo V8. Marca todos os objetos acessíveis a partir das raízes e remove os não marcados. Resolve o problema de referências circulares
Cenários Comuns de Vazamento
- Variáveis globais não intencionais
- Timers (
setInterval) sem cancelamento - Referências a elementos DOM removidos
- Closures com retenção desnecessária de variáveis
- Event listeners não removidos ao destruir componentes
- Em Vue: variáveis globais, timers e eventos não limpos no
beforeDestroy/unmounted
Detecção
Utilizar o Chrome DevTools > Performance > gravar a sessão e clicar em "Collect garbage" para identificar crescimento anormal de memória.
- Event Loop no Navegador vs Node.js
Ambos seguem o mesmo princípio básico: executar código síncrono, processar microtasks e então processar macrotasks.
Node.js possui diferenças significativas:
- Macrotasks possuem prioridades distintas em 6 fases: timers, I/O callbacks, idle/prepare, poll, check, close callbacks
process.nextTicktem prioridade superior a outras microtasks- Antes de cada fase de macrotasks, todas as microtasks pendentes são processadas
- Virtual DOM é Realmente Rápido?
O Virtual DOM não é mais rápido que manipulação direta do DOM. Porém, oferece uma abordagem otimizada para atualizações baseadas em mudanças de estado, comparando representações virtuais e aplicando apenas as diferenças necessárias ao DOM real.
- Performance: for vs forEach
for é mais rápido que forEach, pois este último invoca uma função callback a cada iteração, criando um novo contexto de execução e adicionando overhead.
- Princípio do JS Bridge
JS Bridge permite que código JavaScript em WebViews se comunique com APIs nativas do aplicativo. Implementações comuns:
- API global exposta: O cliente adiciona métodos ao objeto
window(ex:window.obterVersao()). Operações síncronas - Interceptação de URL Scheme: Protocolos customizados (ex:
minha-app://api/dados) são interceptados pelo aplicativo que processa e retorna resultados
- requestIdleCallback vs requestAnimationFrame
- requestAnimationFrame: Executa antes de cada repaint, ideal para animações de alto desempenho
- requestIdleCallback: Executa durante períodos ociosos do navegador, ideal para tarefas de baixa prioridade como pré-carregamento de dados
- Atraso de 300ms em Clique Mobile
Navegadores antigos aguardavam 300ms para detectar double-tap (zoom). Soluções:
- Configurar
<meta name="viewport" content="width=device-width, initial-scale=1.0">(Chrome 32+) - Utilizar bibliotecas como FastClick para navegadores mais antigos
- Token vs Cookie em Autenticação
| Aspecto | Cookie | Token (JWT) |
|---|---|---|
| Envio | Automático pelo navegador | Manual (header Authorization) |
| Cross-origin | Limitado por domínio | Sem restrições |
| Carga no servidor | Alta (verificação a cada requisição) | Baixa (verificação local) |
| Armazenamento de dados | Limitado (~4KB) | Flexível |
- Autenticação: Session vs JWT
Baseado em Session
- Usuário envia credenciais ao servidor
- Servidor cria sessão e armazena em banco de dados
- Servidor envia sessionId via Set-Cookie
- Requisições subsequentes incluem o cookie automaticamente
Prós: Simples, dados no servidor. Contras: Consome memória do servidor, difícil sincronização em múltiplos servidores.
Baseado em JWT
- Usuário envia credenciais ao servidor
- Servidor gera token JWT com informações do usuário
- Cliente armazena e envia token no header Authorization
- Servidor apenas verifica a validade do token
Prós: Stateless, sem restrição cross-origin, escalável. Contras: Tokens maiores, dificuldade em invalidar tokens individualmente.
- Single Sign-On (SSO)
SSO permite que usuários acessem múltiplos sistemas com uma única autenticação.
Implementação para mesmo domínio
Configurar cookie com domain para o domínio raiz:
document.cookie = "sessionId=abc123; domain=exemplo.com; path=/"
Implementação para domínios diferentes
Utilizar um centro de autenticação centralizado:
-
Usuário acessa siteA.com e é redirecionado ao centro de autenticação
-
Após login, o centro gera token e redireciona de volta com o token na URL
-
Ao acessar siteB.com, o centro verifica o cookie existente e fornece o token
-
O siteB valida o token com o centro e armazena localmente
-
Atributos defer e async na tag script
- Sem atributo: Pausa HTML, baixa e executa o script imediatamente
- async: Download paralelo com HTML, execução pausa o HTML
- defer: Download paralelo com HTML, execução somente após conclusão da análise do HTML
- preload vs prefetch
- preload: Carrega recursos críticos da página atual com alta prioridade. Bloqueia renderização até conclusão
- prefetch: Carrega recursos que podem ser necessários futuramente, em segundo plano com baixa prioridade
<!-- Recursos críticos para a página atual -->
<link rel="preload" href="estilos.css" as="style">
<link rel="preload" href="principal.js" as="script">
<!-- Recursos para navegação futura -->
<link rel="prefetch" href="proxima-pagina.js" as="script">
- dns-prefetch e preconnect
- dns-prefetch: Resolve o endereço DNS antecipadamente
- preconnect: Estabelece conexão completa (DNS + TCP + TLS) antecipadamente
Úteis para reduzir latência ao acessar recursos de terceiros.
- WebSocket vs HTTP
- HTTP: Protocolo stateless, comunicação unidirecional (cliente inicia), nova conexão a cada requisição
- WebSocket: Conexão persistente, comunicação bidirecional em tempo real, ideal para chat, jogos online e atualizações em tempo real
- Comunicação entre Abas do Navegador
Via localStorage
// Tab 1 - Escrita
localStorage.setItem('notificacao', JSON.stringify({ tipo: 'atualizacao' }));
// Tab 2 - Leitura via evento
window.addEventListener('storage', (evento) => {
if (evento.key === 'notificacao') {
const dados = JSON.parse(evento.newValue);
console.log('Atualização recebida:', dados);
}
});
Via BroadcastChannel
// Tab 1
const canal = new BroadcastChannel('comunicacao');
canal.postMessage({ acao: 'atualizar' });
// Tab 2
const canal = new BroadcastChannel('comunicacao');
canal.onmessage = (evento) => {
console.log('Mensagem recebida:', evento.data);
};
- Comunicação entre iframes
Utilizar postMessage para comunicação segura:
// Enviar mensagem do iframe para o pai
window.parent.postMessage({ tipo: 'status', valor: 'pronto' }, 'https://dominio-pai.com');
// Pai escutando mensagens do iframe
window.addEventListener('message', (evento) => {
if (evento.origin === 'https://dominio-filho.com') {
console.log('Dados recebidos:', evento.data);
}
});
// Pai enviando mensagem para o iframe
document.getElementById('meuIframe').contentWindow.postMessage(
{ tipo: 'comando', acao: 'carregar' },
'https://dominio-filho.com'
);
- Otimização de Primeira Tela (First Contentful Paint)
- Lazy loading de rotas: Carregar apenas os componentes necessários para a rota atual
- Renderização no servidor (SSR): Nuxt.js para Vue, Next.js para React
- Pré-busca no aplicativo: Em WebViews, o app nativo pode pré-carregar dados
- Paginação: Carregar conteúdo incrementalmente conforme necessário
- Lazy loading de imagens: Atrasar carregamento de imagens fora da viewport
- Pacotes offline: Armazenar recursos estáticos localmente no aplicativo
- Renderização de Grande Volume de Dados
Para 100.000 itens de dados, utilizar lista virtual:
- Renderiza apenas itens visíveis na viewport
- Elementos fora da view são representados por um container com altura calculada
- Ao rolar, itens são criados e destruídos dinamicamente
- Bibliotecas recomendadas:
vue-virtual-scroller,react-virtualized,react-window
- Diagnóstico de Performance em H5
Ferramentas
- Chrome DevTools (Performance): Métricas como FP, FCP, LCP, DCL e window.onload
- Lighthouse: Ferramenta automatizada com relatório detalhado e sugestões de otimização
# Instalação e uso do Lighthouse
npm install -g lighthouse
lighthouse https://exemplo.com --view --preset=mobile
Identificação de Problemas
- Carregamento lento: Otimizar APIs, usar CDN, comprimir assets, code splitting
- Renderização lenta: SSR, otimização de first paint, skeleton screens
- Questões Avançadas de Código
Propriedades de Construtor e Protótipo
function Veiculo() {
Veiculo.tipo = function() { console.log('estatico') };
this.tipo = function() { console.log('instancia') };
}
Veiculo.prototype.tipo = function() { console.log('prototipo') };
Veiculo.tipo = function() { console.log('inicial') };
Veiculo.tipo(); // "inicial"
const carro = new Veiculo();
carro.tipo(); // "instancia"
Veiculo.tipo(); // "estatico"
Explicação: Primeiro, Veiculo.tipo() executa a atribuição estática inicial, imprimindo "inicial". Após new Veiculo(), o construtor redefine Veiculo.tipo e adiciona método à instância. carro.tipo() encontra o método na instância. Veiculo.tipo() agora executa a versão redefinida pelo construtor.
Encadeamento de Promises
Promise.resolve().then(() => {
console.log('A');
return Promise.resolve('B');
}).then((resultado) => {
console.log(resultado);
});
Promise.resolve().then(() => {
console.log('C');
}).then(() => {
console.log('D');
}).then(() => {
console.log('E');
}).then(() => {
console.log('F');
}).then(() => {
console.log('G');
});
// Saída: A, C, D, E, B, F, G
Quando then retorna uma Promise, ocorre um atraso de dois ciclos ("slow two beats"): aguarda a resolução da Promise interna e depois agenda o próximo then como microtask.
Atribuição Encadeada e Prioridade do Operador
let objA = { valor: 1 };
let objB = objA;
objA.prop = objA = { valor: 2 };
console.log(objA.prop); // undefined
console.log(objB.prop); // { valor: 2 }
Explicação: O operador . tem prioridade sobre =. Primeiro, objA.prop é inicializado como undefined no objeto original { valor: 1 }. Depois, a atribuição encadeada é executada da direita para a esquerda: objA recebe { valor: 2 }, e objA.prop (referindo-se ao objeto original via objB) recebe a nova referência.
Chaves de Objetos e Conversão de Tipos
// Exemplo 1: Números como chaves
const dados1 = {};
const chave1 = '123';
const chave2 = 123;
dados1[chave1] = 'a';
dados1[chave2] = 'b';
console.log(dados1[chave1]); // "b" (123 é convertido para "123")
// Exemplo 2: Symbols como chaves
const dados2 = {};
const simboloA = Symbol('id');
const simboloB = Symbol('id');
dados2[simboloA] = 'x';
dados2[simboloB] = 'y';
console.log(dados2[simboloA]); // "x" (Symbols são únicos)
// Exemplo 3: Objetos como chaves
const dados3 = {};
const obj1 = { nome: 'teste' };
const obj2 = { nome: 'outro' };
dados3[obj1] = 'alpha';
dados3[obj2] = 'beta';
console.log(dados3[obj1]); // "beta" (ambos convertem para "[object Object]")
Propriedades de objetos em JavaScript são convertidas para strings. Números tornam-se strings numéricas, objetos invocam toString() resultando em "[object Object]", e Symbols mantêm sua unicidade.