W3docs

Entendendo a Conversão de Object para Primitivo em JavaScript

Aprenda como o JavaScript converte objects em primitivos: hints string, number e default, o método Symbol.toPrimitive e a cadeia de fallback toString/valueOf.

Introdução à Conversão de Object para Primitivo

Em JavaScript, objects são valores de referência, mas muitas operações esperam um primitivo (uma string, um número ou um boolean). Quando você escreve obj + "", +obj ou `${obj}`, a linguagem precisa primeiro converter o object em um primitivo antes de executar a operação. Isso é chamado de conversão de object para primitivo.

Este guia explica as regras que o JavaScript segue: os três hints de conversão ("string", "number", "default"), o método Symbol.toPrimitive que permite controlar a conversão, e a cadeia de fallback toString()/valueOf() usada quando Symbol.toPrimitive está ausente.

Como Funciona a Conversão de Object para Primitivo

Não existe operador que converta um object em um boolean — objects são sempre truthy em um contexto boolean. Portanto, a conversão de object para primitivo só produz uma string ou um number, e o JavaScript decide qual alvo usar passando um hint ao object:

  1. Primeiro, ele procura um método [Symbol.toPrimitive](hint). Se presente, ele é chamado e seu valor de retorno (que deve ser um primitivo) é utilizado.
  2. Se Symbol.toPrimitive estiver ausente, o JavaScript recorre a toString() e valueOf(), chamando-os em uma ordem que depende do hint.

Veremos o fallback em detalhes mais adiante. Primeiro, a abordagem moderna e explícita.

Exemplo: Implementando Symbol.toPrimitive

javascript— editable

Explicação: O object user define um único método Symbol.toPrimitive que ramifica com base no hint. Um template literal solicita o hint "string", a multiplicação solicita "number", e o operador binário + solicita "default". Retornar this.money para o caso padrão mantém a consistência da aritmética com + e *.

Entendendo os Hints de Conversão

Um hint é uma string que o motor passa para indicar ao seu object que tipo de primitivo a operação prefere:

  • "string": espera-se que o resultado seja uma string — String(obj), `${obj}`, alert(obj), ou um object usado como chave de propriedade.
  • "number": espera-se um resultado numérico — +obj unário, obj * 2, obj - 1, obj < other, Number(obj), Math.round(obj).
  • "default": o operador aceita qualquer tipo e não tem certeza de qual solicitar. Isso é mais raro do que as pessoas esperam, mas importa: o operador binário + (que pode significar tanto adição quanto concatenação de string) usa "default", assim como os operadores de igualdade fraca == / != ao comparar um object com um número ou string.

Uma surpresa comum: obj + "" não usa o hint "string" — usa "default". Se você apenas trata "string" e "number", o branch "default" é o que é executado para +.

Exemplo: Tratando Diferentes Hints

javascript— editable

Explicação: Aqui o object item trata todos os três hints. Observe a última linha: como o operador binário + usa o hint "default", item + '' executa o branch "default" — não o branch "string" — produzindo "Item: Chair, Price: 45". Esse é exatamente o tipo de sutileza que torna valer a pena tratar cada hint explicitamente. Veja também operadores de comparação e operadores numéricos.

O Fallback toString / valueOf

Se um object não possui o método Symbol.toPrimitive, o JavaScript usa o par de métodos mais antigo e escolhe uma ordem com base no hint:

  • Para o hint "string": tenta toString() primeiro, depois valueOf().
  • Para o hint "number" ou "default": tenta valueOf() primeiro, depois toString().

Em cada caso, usa o primeiro método que retorna um primitivo; se um método retorna um object, ele é ignorado e o próximo é tentado. Um object simples herda Object.prototype.toString (que retorna "[object Object]") e Object.prototype.valueOf (que retorna o próprio object, portanto é ignorado) — é por isso que ({}) + "" resulta em "[object Object]".

javascript— editable

Explicação: Sem Symbol.toPrimitive, o hint "string" chega em toString() e retorna "John", enquanto os hints numérico e padrão chegam em valueOf() e retornam 1000. Symbol.toPrimitive é preferido em código novo porque oferece um lugar único e explícito para tratar cada hint; toString/valueOf continuam úteis quando você só se preocupa com uma direção.

Boas Práticas para Usar toPrimitive

Implementar Symbol.toPrimitive efetivamente envolve uma combinação de clareza, consistência e testes completos para garantir que os objects se comportem de forma previsível ao serem convertidos em primitivos. Veja como você pode aplicar essas boas práticas ao usar o método Symbol.toPrimitive:

1. Semântica Clara

Boa Prática: Defina Symbol.toPrimitive de forma clara para tornar as conversões de object previsíveis e compreensíveis. Isso envolve tratar explicitamente os diferentes tipos de hints de conversão ("string", "number" e "default") e fornecer valores de retorno adequados para cada caso.

Exemplo:

javascript— editable

Explicação: Neste exemplo, o object dateEvent define claramente os comportamentos de conversão para os contextos de string e number. Para conversões em string, retorna uma declaração descritiva, e para conversões em number, retorna o timestamp do evento. Essa distinção clara ajuda outros desenvolvedores a entender o que esperar ao converter o object em diferentes contextos.

2. Consistência

Boa Prática: Garanta que as conversões sejam consistentes com os dados do object e seu uso pretendido, evitando comportamentos confusos ou ilógicos.

javascript— editable

Explicação: O object product garante que a lógica de conversão seja consistente com suas propriedades. Seja ao ser convertido em string para exibição ou em number para cálculos, o resultado permanece intuitivo e útil, respeitando o uso pretendido de cada propriedade.

3. Testes

Boa Prática: Teste minuciosamente como seus objects se comportam em diferentes cenários de conversão para evitar bugs inesperados em sua aplicação.

Exemplos de Abordagens de Teste:

  • Testes Unitários: Escreva testes unitários que tentem converter o object usando diferentes operações (como operações aritméticas, concatenação de string ou passando o object para funções que esperam um tipo primitivo) para garantir que todos os cenários retornem os valores esperados.
// Note: In a browser environment, use console.assert or a test framework like Jest/Mocha.
// Assumes 'product' is defined as in the previous example.
console.assert(String(product) === "Laptop costs $1200", "String conversion failed");
console.assert(+product === 1200, "Number conversion failed");
console.assert(product + '' === "Laptop", "Default conversion failed");

Explicação: Por meio de testes unitários, você pode verificar que o object product trata corretamente todas as formas de conversão de acordo com a lógica especificada em Symbol.toPrimitive. Isso ajuda a garantir confiabilidade e consistência na forma como seu object interage com diferentes partes do motor JavaScript e da sua aplicação.

Armadilhas Comuns

  • Não existe hint boolean. Em um contexto boolean (if (obj), !obj, obj && x) o object é sempre truthy e nunca é convertido em um primitivo. A conversão de object para primitivo só produz strings e numbers.
  • + usa "default", não "string". Isso pega muitos desenvolvedores de surpresa: obj + "" aciona o hint padrão. Comparações como obj == 5 também usam "default".
  • Um método deve retornar um primitivo. Se Symbol.toPrimitive (ou valueOf/toString) retornar um object em vez de um primitivo, você receberá um TypeError. Para o par de fallback, retornar um object simplesmente faz com que esse método seja ignorado.
  • A conversão numérica de um resultado string pode produzir NaN. Se o seu branch "number"/"default" retornar uma string não numérica, contextos que esperam um número obterão NaN: +{ [Symbol.toPrimitive]: () => "abc" } é NaN.

Conclusão

A conversão de object para primitivo é um mecanismo central do JavaScript que permite que objects participem de operações aritméticas, concatenação de string e comparações. O motor escolhe um hint ("string", "number" ou "default"), tenta Symbol.toPrimitive primeiro e, caso contrário, recorre a toString()/valueOf(). Ao implementar Symbol.toPrimitive, você ganha um lugar único e explícito para controlar como um object personalizado se comporta em cada contexto — resultando em um código mais previsível e fácil de manter. Para se aprofundar, revise tipos de dados, tipos symbol e métodos de object e this.

Prática

Prática
Ao avaliar obj + '' e obj possui um método Symbol.toPrimitive, qual hint o JavaScript passa?
Ao avaliar obj + '' e obj possui um método Symbol.toPrimitive, qual hint o JavaScript passa?
Prática
Se um object não possui o método Symbol.toPrimitive, qual ordem o JavaScript tenta para o hint 'number'?
Se um object não possui o método Symbol.toPrimitive, qual ordem o JavaScript tenta para o hint 'number'?
Was this page helpful?