Identidade e caminhos partilhados
Duas variáveis podem conter a referência ao mesmo objeto. Alterar uma propriedade através de uma delas torna a alteração visível pela outra. Criar { ...original } produz um objeto exterior distinto, mas os valores que são referências continuam a apontar para os objetos interiores originais.
Desenha os caminhos original.policy e copy.policy antes de prever uma alteração em retries. A igualdade estrita dos objetos exteriores pode ser false e a dos objetos interiores ser true. Não uses uma igualdade de campos como prova de identidade, nem uma diferença de identidade exterior como prova de isolamento completo.
Propriedade própria e valor disponível
Uma leitura como option.enabled pode obter um valor do protótipo. Isso é útil para reutilizar métodos, mas não demonstra que um campo veio no próprio registo. Object.hasOwn(option, "enabled") verifica a propriedade própria. O operador in também inclui a cadeia de protótipos.
Numa regra que exige uma opção própria e booleana, verifica separadamente origem e valor: Object.hasOwn(option, "enabled") && option.enabled === true. Assume aqui um objeto comum já delimitado, sem getters ou proxies. Não chames option.hasOwnProperty como se essa propriedade fosse necessariamente uma função fiável: dados importados podem substituí-la ou o objeto pode ter protótipo null.
Predefinições e acessos opcionais
Em const { timeout = 30 } = input, a predefinição aplica-se quando o valor extraído é undefined. Null permanece null. Decide se null significa ausência permitida, valor explícito ou erro do contrato; a sintaxe não toma essa decisão por ti.
Uma cadeia como input?.meta?.name protege os dois pontos que podem ser null ou undefined. Em (input?.meta).name, os parênteses terminam a cadeia protegida: a leitura exterior pode tentar aceder a undefined. Mesmo uma leitura sem exceção não valida o tipo de name. Se o destino exige uma string, mantém essa validação como etapa explícita.
O que spread realmente copia
Spread num literal de objeto percorre propriedades próprias enumeráveis, lê os seus valores e cria propriedades de dados no destino. Um getter elegível é executado durante essa leitura; o seu descritor de acesso não é transportado para o destino. O protótipo e os métodos que nele residem também não são copiados.
A ordem interessa: { ...defaults, ...supplied } permite que uma propriedade própria de supplied substitua o valor anterior, incluindo por undefined. Para rever uma configuração, separa política de precedência, tratamento de ausência e validação. Trocar a ordem dos spreads muda a precedência, mas não resolve sozinho um contrato de entrada mal definido.
Freeze, classes e recetores
Object.freeze atua no objeto recebido, impedindo extensões e tornando não graváveis as suas propriedades de dados. Não cria uma cópia nem percorre os objetos interiores. Uma escrita numa propriedade de dados congelada lança TypeError em modo estrito; uma escrita num objeto interior ainda mutável pode ter sucesso.
Uma instância de classe pode combinar propriedades próprias, métodos no protótipo e elementos privados. Espalhar a instância num objeto comum não recria essa estrutura. Um método que lê this.#count exige um recetor com o elemento privado correspondente; fornecer { count: 4 } através de call não satisfaz esse requisito. Analisa o recetor real antes de atribuir a falha ao valor público dos campos.
Exemplo: fotografia de configuração
O exemplo captureSettings trabalha com um esquema pequeno e previamente validado. Copia os números timeout e retries, cria um novo objeto policy e um novo array tags. Como os elementos de tags são strings, o array novo não conserva elementos mutáveis partilhados com a origem.
Depois de alterar current.policy.retries para 4 e acrescentar "urgent", saved conserva retries igual a 2 e tags igual a ["daily"]. A função não é um clonador genérico: um campo extra, um objeto dentro de tags ou um getter exigiriam rever o contrato. Também não congela saved; a independência da origem não impede uma futura alteração direta da fotografia.
Revisão orientada por alterações
Para aceitar uma cópia, enumera as alterações que devem ficar isoladas: substituir uma propriedade exterior, alterar um campo interior e acrescentar um elemento a um array são operações diferentes. Traça cada uma até ao objeto efetivamente modificado. Escreve depois o valor que o consumidor histórico deverá continuar a observar.
Nas perguntas, distingue três diagnósticos: partilha acidental de referências, propriedade obtida por herança e recetor incompatível com um método. Uma solução que só evita uma exceção pode continuar a aceitar dados errados. Uma solução que congela um objeto exterior pode continuar a deixar referências interiores partilhadas. A decisão deve cumprir o contrato completo declarado no cenário.
function captureSettings(settings) {
return {
timeout: settings.timeout,
policy: { retries: settings.policy.retries },
tags: [...settings.tags]
};
}
const current = { timeout: 5, policy: { retries: 2 }, tags: ["daily"] };
const saved = captureSettings(current);
current.policy.retries = 4;
current.tags.push("urgent");Lê o excerto assumindo dados já validados: timeout e retries numéricos, tags como array denso de strings, sem outros campos. Prevê saved.policy.retries e saved.tags depois das duas alterações; explica que referências foram recriadas.
Armadilhas comuns
Confundir const com imutabilidade; assumir que spread clona objetos interiores; aceitar propriedades herdadas como próprias; esperar que freeze seja recursivo.
Tópicos relacionados: Variáveis, funções e closures · Arrays, coleções e iteração · Erros, validação e contratos de falha
Desenha o contrato de propriedade e de cópia. Uma nova referência exterior não prova que os dados interiores ficaram independentes.
Referência: CopyDataProperties · ECMAScript 2026 (17th edition); browser APIs identified separately