Skip to content

Object to primitive conversion #193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Prev Previous commit
Next Next commit
Update Object-toPrimitive translation.
  • Loading branch information
odsantos committed Nov 11, 2020
commit 26f1fd0324f035091b63c03f98c163c337d2d6c1
204 changes: 111 additions & 93 deletions 1-js/04-object-basics/09-object-toprimitive/article.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@

# Conversão objeto para primitivo

O que acontece quando objetos são adicionados `obj1 + obj2`, subtraídos `obj1 - obj2`, ou imprimidos usando `alert(obj)`?
O que acontece quando objetos são adicionados `obj1 + obj2`, subtraídos `obj1 - obj2`, ou exibidos usando `alert(obj)`?

Existem métodos especiais de objetos que fazem a conversão.
Aí, os objetos são auto-convertidos para primitivos, e depois a operação é executada.

No capítulo <info:type-conversions> vimos as regras para as conversões de primitivos para números, *strings* e boleanos. Mas, deixámos um intervalo para objetos. Agora, como já aprendemos sobre métodos e símbolos, torna-se possível fechá-lo.
No capítulo <info:type-conversions> vimos regras para as conversões de primitivos para números, *strings* e booleanos. Mas, deixámos um intervalo para objetos. Agora, como já aprendemos sobre métodos e símbolos, se torna possível o fechar.

Para objetos, não há conversão para boleanos, porque todos os objetos são `true` num contexto boleano. Assim, apenas existem conversões para *strings* e números.

A conversão numérica acontece quando subtraímos objetos, ou aplicamos funções matemáticas. Por exemplo, os objetos `Date` (a serem estudados no capítulo <info:date>) podem ser subtraídos, e o resultado de `date1 - date2` é a diferença temporal entre as datas.

Para a conversão para *string* -- ela geralmente acontece quando imprimimos um objeto, como em `alert(obj)`, e em contextos similares.
1. Todos os objetos são `true` num contexto booleano. Apenas existem as conversões para *strings* e numérica.
2. A conversão numérica acontece quando subtraímos objetos, ou aplicamos funções matemáticas. Por exemplo, os objetos `Date` (a serem estudados no capítulo <info:date>) podem ser subtraídos, e o resultado de `date1 - date2` é a diferença temporal entre duas datas.
3. Para a conversão para *string* -- ela geralmente acontece quando exibimos um objeto, como em `alert(obj)`, e em contextos similares.

## *ToPrimitive*

Quando um objeto é utilizado num contexto onde um primitivo é necessário, por exemplo num `alert` ou em operações matemáticas, é convertido para um valor primitivo usando o algoritmo de `ToPrimitive` ([especificação em Inglês](https://tc39.github.io/ecma262/#sec-toprimitive)).

Esse algoritmo, permite-nos personalizar a conversão empregando um método de objeto especial.
Nós, podemos afinar as conversões para *string* e numérica empregando métodos de objeto especiais.

Dependendo do contexto, tem o que se chama de "palpite" (*hint*).

Existem três variantes:
Existem três variantes de conversão de tipo de dados, também chamadas de "sugestões" (*hints*), descritas na [especificação (em Inglês)](https://tc39.github.io/ecma262/#sec-toprimitive):

`"string"`
: Quando uma operação espera uma *string*, desta forma em conversões objeto-para-string, como em `alert`:
: Para uma conversão objeto-string, quando estivermos a fazer uma operação num objeto mas à espera de uma *string* como resultado, a exemplo de `alert`:

```js
// saída
// exibindo
alert(obj);

// empregando o objeto como chave de propriedade
// usando o objeto como chave de propriedade
anotherObj[obj] = 123;
```

`"number"`
: Quando uma operação espera um número, desta forma em conversões objeto-para-número, como em operações matemáticas:
:Para uma conversão objeto-número, como em operações matemáticas:

```js
// conversão explícita
Expand All @@ -52,37 +46,43 @@ Existem três variantes:
`"default"`
: Ocorre em casos raros, quando o operador "não está certo" de que tipo esperar.

Por exemplo, o mais binário `+` pode trabalhar tanto com *strings* (concatenando-as) como com números (adicionando-os); portanto, quer *stings* como números são aceites. Ou, quando um objeto é comparado `==` a uma *string*, a um número, ou a um símbolo.
Por exemplo, o mais binário `+` pode trabalhar tanto com *strings* (as concatena) como com números (os adiciona), portanto quer *stings* como números são aceites. Assim, se um mais binário tiver um objeto como argumento, ele utiliza a sugestão `"default"` para o converter.

De igual modo, se um objeto for comparado a uma *string*, a um número ou a um símbolo usando `==`, também não está claro que conversão deve ser feita, então a sugestão `"default"` é utilizada.

```js
// 'mais' binário
// o 'mais' binário usa a sugestão "default"
let total = car1 + car2;

// obj == string/número/símbolo
// obj == number usa a sugestão "default"
if (user == 1) { ... };
```

Os operadores maior/menor do que `<>` também podem trabalhar com *strings* e números. Assim, aceitam o palpite "number", mas não o "default". Isso, por razões históricas.
Os operadores de comparação maior/menor do que, tais como `<` `>`, também podem trabalhar tanto com *strings* como com números. Contudo, eles usam a sugestão `"number"`, não a `"default"`. Isto, por razões históricas.

Na prática, todos os objetos incorporados (*built-in*), exceto num caso (o objeto `Date`, sobre o qual aprenderemos mais adiante) implementam a conversão `"default"` da mesma forma que a `"number"`. E, nós provavelmente deveriamos fazer o mesmo.
Na prática, na verdade, nós não precisamos de nos lembrar desses pequenos detalhes, porque todos os objetos incorporados, exceto num caso (o objeto `Date`, sobre o qual iremos aprender mais adiante) implementam a conversão `"default"` da mesma forma que a `"number"`. E nós podemos fazer o mesmo.

Por favor note -- existem apenas três palpites (*hints*). É assim tão simples. Não há um palpite para "boleano" (todos os objetos são `true` num contexto boleano), ou outro adicional. E, se não fizermos distinção entre `"default"` e `"number"`, como muitos incorporados (*built-ins*) não fazem, então apenas existem duas conversões.
```smart header="Nenhuma sugestão `\"boolean\"`"
Por favor note -- existem apenas três sugestões (*hints*). É assim tão simples.

**Para efetuar a conversão, JavaScript tenta encontrar e chama três métodos de objeto:**
Não há uma sugestão para "booleano" (todos os objetos são `true` num contexto booleano), nem outras sugestões. E, se não fizermos distinção entre `"default"` e `"number"`, como muitos incorporados não fazem, então apenas existem duas conversões.
```

**Para efetuar a conversão, o JavaScript tenta encontrar e chama três métodos de objeto:**

1. Chama `obj[Symbol.toPrimitive](hint)` se o método existir,
2. Senão, se o palpite (*hint*) for `"string"`
2. Senão, se a sugestão for `"string"`
- tenta `obj.toString()` e `obj.valueOf()`, o que existir.
3. Senão, se o palpite for `"number"` ou `"default"`
3. Senão, se a sugestão for `"number"` ou `"default"`
- tenta `obj.valueOf()` e `obj.toString()`, o que existir.

## *Symbol.toPrimitive*

Vamos começar pelo primeiro método. Existe um símbolo incorporado (*built-in*) chamado `Symbol.toPrimitive` que deverá ser utilizado para nomear o método de conversão, desta forma:
Vamos começar pelo primeiro método. Existe um símbolo incorporado chamado `Symbol.toPrimitive` que deverá ser utilizado para dar nome ao método de conversão, desta forma:

```js
obj[Symbol.toPrimitive] = function(hint) {
// retorna um valor primitivo
// tem de retornar um valor primitivo
// hint = "string", ou "number", ou "default"
}
```
Expand All @@ -95,54 +95,78 @@ let user = {
money: 1000,

[Symbol.toPrimitive](hint) {
alert(`palpite: ${hint}`);
alert(`sugestão: ${hint}`);
return hint == "string" ? `{nome: "${this.name}"}` : this.money;
}
};

// demonstrações de conversões:
alert(user); // (palpite: string) -> {nome: "John"}
alert(+user); // (palpite: number) -> 1000
alert(user + 500); // (palpite: default) -> 1500
// exemplos de conversões:
alert(user); // sugestão: string -> {nome: "John"}
alert(+user); // sugestão: number -> 1000
alert(user + 500); // sugestão: default -> 1500
```

Como podemos observar pelo código, `user` se torna numa *string* auto-descritiva ou numa quantia monetária, dependendo da conversão. Um único método `user[Symbol.toPrimitive]` trata de todos os casos de conversão.
Como podemos ver pelo código, `user` se torna numa *string* auto-descritiva ou numa quantia monetária, dependendo da conversão. Um único método `user[Symbol.toPrimitive]` trata de todos os casos de conversão.


## *toString/valueOf*

Os métodos `toString` e `valueOf` vêm de tempos antigos. Eles não são *symbols* (símbolos não existiam há tanto tempo), mas sim métodos com nomes "comuns". Eles fornecem uma alternativa "à moda antiga" para implementar a conversão.

Se não houver `Symbol.toPrimitive` então JavaScript tenta encontrá-los na seguinte ordem:
Se não houver `Symbol.toPrimitive` então o JavaScript os tenta encontrar na seguinte ordem:

- `toString -> valueOf` para a sugestão "string".
- `valueOf -> toString` para as outras.

Estes métodos têm de retornar um valor primitivo. Se `toString` ou `valueOf` retornarem um objeto, este é ignorado (como se o método não existisse).

Por padrão, um objeto simples possui os seguintes métodos `toString` e `valueOf`:

- O método `toString` retorna uma string `"[object Object]"`.
- O método `valueOf` retorna o próprio objeto.

Aqui está um exemplo:

```js run
let user = {name: "John"};

alert(user); // [object Object]
alert(user.valueOf() === user); // true
```

Assim, se tentarmos usar um objeto como uma *string*, como em `alert` ou similar, então por padrão nós iremos ver `[object Object]`.

E o valor padrão de `valueOf` está aqui mencionado apenas por completude, para evitar qualquer confusão. Como você pode ver, ele retorna o próprio objeto, e por isso este é ignorado. Não me pergunte porquê, é por razões históricas. Então, podemos assumir que o método não existe.

- `toString -> valueOf`, para o palpite "string".
- `valueOf -> toString`, para os outros.
Vamos implementar estes métodos.

Por exemplo, aqui `user` faz o mesmo que acima empregando uma combinação `toString` e `valueOf`:
Por exemplo, aqui `user` faz o mesmo que acima usando uma combinação de `toString` e `valueOf` em vez de `Symbol.toPrimitive`:

```js run
let user = {
name: "John",
money: 1000,

// para o palpite="string"
// para hint="string"
toString() {
return `{nome: "${this.name}"}`;
},

// para o palpite="number" ou "default"
// para hint="number" ou "default"
valueOf() {
return this.money;
}

};

alert(user); // (toString) -> {nome: "John"}
alert(+user); // (valueOf) -> 1000
alert(user + 500); // (valueOf) -> 1500
alert(user); // toString -> {nome: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
```

Frequentemente, queremos um local "genérico" (*catch-all*) que trate de todas as conversões para primitivos. Neste caso, podemos apenas implementar `toString`, desta forma:
Como podemos ver, o resultado é o mesmo que no exemplo anterior com `Symbol.toPrimitive`.

Frequentemente, queremos um único local "genérico" (*catch-all*) para tratar de todas as conversões para primitivos. Nesse caso, podemos só implementar `toString`, desta forma:

```js run
let user = {
Expand All @@ -153,85 +177,79 @@ let user = {
}
};

alert(user); // (toString) -> John
alert(user + 500); // (toString) -> John500
alert(user); // toString -> John
alert(user + 500); // toString -> John500
```

Na ausência de `Symbol.toPrimitive` e `valueOf`, `toString` tratará de todas as conversões para primitivos.

Na ausência de `Symbol.toPrimitive` e `valueOf`, `toString` irá tratar de todas as conversões para primitivos.

## *ToPrimitive* e *ToString*/*ToNumber*
## Tipos de dados retornados

O importante a saber sobre todos os métodos de conversão para primitivos é que eles não necessariamente retornam o "palpite" de primitivo.
O importante a saber sobre todos os métodos de conversão para primitivos, é que eles não necessariamente retornam o primitivo "sugerido".

Não existe nenhum controlo sobre se `toString()` retorna exatamente uma *string*, ou se o método `Symbol.toPrimitive` retorna um número aquando de um palpite "number".
Não existe nenhum controlo sobre se `toString()` retorna exatamente uma *string*, ou se o método `Symbol.toPrimitive` retorna um número para uma sugestão `"number"`.

**A única coisa mandatória: estes métodos têm que retornar um primitivo.**
A única coisa mandatória: estes métodos têm de retornar um primitivo, não um objeto.

Uma operação que precise da conversão obtem esse primitivo, e a seguir continua a trabalhar com ele, aplicando posteriores conversões se necessário.
```smart header="Notas históricas"
Por razões históricas, se `toString` ou `valueOf` retornarem um objeto, não haverá erro, mas esse valor é ignorado (como se o método não existisse). Isto, porque antigamente não havia um bom conceito de "erro" em JavaScript.

Por exemplo:
Em contraste, `Symbol.toPrimitive` *tem de* retornar um primitivo, caso contrário haverá um erro.
```

- Operações matemáticas (exceto o 'mais' binário) executam uma conversão `ToNumber`:
## Outras conversões

```js run
let obj = {
toString() { // toString trata de todas as conversões, na ausência de outros métodos
return "2";
}
};
Como já sabemos, muitos operadores e funções executam conversões de tipo de dados, por ex. a multiplicação `*` converte os operandos para números.

alert(obj * 2); // 4, ToPrimitive fornece "2", que se torna em 2
```
Se, nós fornecermos um objeto como um argumento, então haverão dois estágios:
1. O objeto é convertido para um primitivo (usando as regras descritas acima).
2. Se, o primitivo resultante não for do tipo certo, é convertido.

- O 'mais' binário verifica o primitivo -- se for uma *string*, então executa a concatenação, noutros casos recorre a `ToNumber` e trabalha com números.
Por exemplo:

Exemplo de string:
```js run
let obj = {
toString() {
return "2";
}
};
```js run
let obj = {
// toString trata de todas as conversões, na ausência de outros métodos
toString() {
return "2";
}
};

alert(obj + 2); // 22 (ToPrimitive retornou uma string => concatenação)
)
```
alert(obj * 2); // 4, objeto convertido para primitivo "2", então a multiplicação o transformou num número
```

Exemplo de número:
```js run
let obj = {
toString() {
return true;
}
};
1. A multiplicação `obj * 2` primeiro converte o objeto para primitivo (esse é a *string* `"2"`).
2. A seguir, `"2" * 2` se torna em `2 * 2` (a *string* é convertida para número).

alert(obj + 2); // 3 (ToPrimitive retornou um boleano, não string => ToNumber)
```
Numa situação semelhante, o 'mais' binário irá concatenar *strings*, pois ele com satisfação aceita uma *string*.

```smart header="Historical notes"
Por razões históricas, os métodos `toString` e `valueOf` *deveriam* retornar um primitivo: se um deles retornar um objeto, não há erro, e esse objeto é ignorado (como se aqueles métodos não existissem).
```js run
let obj = {
toString() {
return "2";
}
};

Em contraste, `Symbol.toPrimitive` *tem* de retornar um primitivo, senão haverá um erro.
alert(obj + 2); // 22 ("2" + 2), a conversão para primitivo retornou uma string => concatenação
```

## Sumário

A conversão objeto-para-primitivo, é automaticamente chamada por muitas funções e operadores incorpordos que esperam um primitivo como valor.
A conversão objeto-para-primitivo, é automaticamente chamada por muitas funções e operadores incorporados que esperam um primitivo como valor.

Existem 3 tipos (*hints*) dela:
- `"string"` (para `alert`, e outras conversões para *string*)
- `"string"` (para `alert`, e outras operações que precisam duma *string*)
- `"number"` (para matemáticas)
- `"default"` (poucos operadores)

A especificação, explícitamente descreve que operador usa qual *hint*. Existem muitos poucos operadores que "não sabem o que esperar" e usam a *hint* `"default"`. Geralmente, para objetos incorporados a *hint* `"default"` é tratada da mesma forma que a `"number"`, pelo que por prática os últimos dois tipos são frequentemente fundidos.
A especificação, explícitamente descreve que operador usa que *hint*. Existem muitos poucos operadores que "não sabem o que esperar" e usam a *hint* `"default"`. Geralmente, para objetos incorporados a *hint* `"default"` é tratada da mesma forma que a `"number"`, pelo que na prática os últimos dois tipos são frequentemente fundidos.

O algoritmo de conversão é:

1. Chame `obj[Symbol.toPrimitive](hint)` se o método existir,
2. Senão, se o tipo é `"string"`
2. Senão, se *hint* for `"string"`
- tente `obj.toString()` e `obj.valueOf()`, o que existir.
3. Senão, se o tipo é `"number"` ou `"default"`
3. Senão, se o *hint* for `"number"` ou `"default"`
- tente `obj.valueOf()` e `obj.toString()`, o que existir.

Na prática, frequentemente basta implementar `obj.toString()` como método "genérico" para todas as conversões que retornem uma representão "legível" de um objeto, quer para propósitos de *logging* como de *debugging*.
Na prática, frequentemente basta apenas implementar `obj.toString()` como método "genérico" para todas as conversões que retornem uma representação "legível" de um objeto, quer para propósitos de *logging* como de *debugging*.