Esse texto é uma tradução livre do artigoJavaScript type coercion explaineddeAlexey Samoshkin.
[Edit 02/05/2018]: Esse post estádisponível em Russo. Palmas aSerj Bulavykpor seus esforços.
Coerção de tipos (type coercion)é o processo de conversão de um valor de um tipo, para outro (como a conversão de uma string para um número, um objeto para um booleano e etc). Qualquer tipo, seja primitivo ou um objeto, é um sujeito válido para coerção de tipo. Para recordar, os primitivos são: number, string, booleano, null, undefined + Symbol (adicionado no ES6).
Como um exemplo de coerção de tipo em prática, veja o linkJavaScript Comparison Table, que mostra como o operador de igualdade==
se comporta para diferentes tipos. Essa matriz parece assustadora devido à coerção de tipo implícita que o operador==
faz, e dificilmente será possível lembrar de todas essas combinações. E você não precisa fazer isso — apenas aprenda os princípios básicos da coerção de tipos.
Esse artigo vai a fundo de como a coerção de tipos funciona no Javascript, e irá prepará-lo com o conhecimento básico para que você possa sentir-se confiante ao explicar sobre as expressões. Ao final do artigo, mostrarei as respostas e as explicarei.
true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
'true' == true
false == 'false'
null == ''
!!"false" == !!"true"
[‘x’] == ‘x’
[] + null + 1
[1,2,3] == [1,2,3]
{}+[]+{}+[1]
!+[]+[]+![]
new Date(0) - 0
new Date(0) + 0
Sim, essa é uma lista boba de coisas que você pode fazer como desenvolvedor. Em 90% dos casos é melhor evitar a coerção de tipos implícita. Considere essa lista como exercícios para aprendizagem para testar seu conhecimento de como a coerção de tipos funciona. Se estiver entendiado, você pode encontrar mais emwtfjs.com.
A propósito, você pode encontrar perguntas disso em entrevistas para vagas de Javascript. Então continue lendo 😄.
Coerção Implícita vs Explícita
Coerção de tipos pode ser explícita ou implícita.
Quando um desenvolvedor deseja converter um tipo escrevendo algo como,Number(valor)
, isso é chamado de*coerção de tipos explícita(explicit type coercionoutype casting.*
Já que o Javascript é uma linguagem fracamente tipada (weakly-typed language), valores também podem ser convertidos entre diferentes tipos automaticamente, e isso é chamado de*coerção de tipos implícita (implicit type coercion).*Isso acontece quando você atribui operados para valores de diferentes tipos, como1 == null
,2/’5'
,null + new Date()
, ou isso pode decorrer do contexto, como usarif (value) {…}
, ondevalue
é forçado a retornar um booleano.
Um operador que não desencadeia a coerção de tipos implítica é===
, que é chamado de operador restrito de igualdade (strict equality operator). O operador de igualdade==
por outro lado, faz a comparação e ativa a coerção de tipos, se necessário.
Coerção de tipo implícito é uma faca de dois gumes: é uma grande fonte de frustração e defeitos, mas também um mecanismo útil que nos permite escrever menos código sem perder a legibilidade.
Três tipos de conversão
A primeira regra que precisamos saber é que existem apenas 3 tipos de conversão no Javascript:
- para string;
- para boolean;
- para number.
A segunda, é que a lógica para conversão de tipos primitivos e objetos funcionam de forma diferente, mas ambos só podem ser convertido nessas 3 maneiras.
Vamos começar primeiro com os primitivos.
Conversão de String
Para indicar a conversão explícita de valores para string use a funçãoString()
. A coerção implícita é ativada pelo operador binário+
, quando qualquer operando é uma string:
String(123) // explícito
123 + '' // implícito
Todos os valores primitivos são convertidos em strings naturalmente, como você poderia esperar:
String(123) // '123'
String(-12.3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
A conversão de Symbol é um pouco complicada, porque só pode ser convertida explicitamente, mas não implicitamente. Leia mais nas regras de coerção de tipos doSymbol.
String(Symbol('my symbol')) // 'Symbol(my symbol)'
'' + Symbol('my symbol') // TypeError é lançado
Conversão de Boolean
Para indicar a conversão explícita de valores para boolean use a funçãoBoolean()
. A conversão implícita ocorre no contexto lógico ou é ativada por operadores lógicos (||&&!
) .
Boolean(2) // explícito
if (2) { ... } // implícito devido ao contexto lógico
!!2 // implícito devido ao operador lógico
2 || 'hello' // implícito devido ao operador lógico
Observação: Operadores lógicos como||
e&&
fazem conversões booleanas internamente, masna verdade retornam o valor dos operandosoriginais, mesmo que eles não sejam booleanos.
// retorna o número 123 ao invés de true
// 'hello' e 123 são convertidos para boolean internamente para calcular a expressão
let x = 'hello' && 123; //x === 123 é true
Assim que houver apenas dois resultados possíveis da conversão booleana:true
oufalse
, é mais fácil lembrar a lista de valores falsos (falsy values).
Boolean('') // false
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(false) // false
Qualquer valor não inserido nessa lista ao ser convertido serátrue
, incluindo objetos, funções,Array
,Date
, tipos definidos pelo usuário e assim por diante. Symbols são considerados como valores verdadeiros (truthy values). Objetos vazios e arrays também:
Boolean({}) // true
Boolean([]) // true
Boolean(Symbol()) // true
!!Symbol() // true
Boolean(function() {}) // true
Conversão Numérica
Para uma conversão explícita aplique a funçãoNumber()
, assim como feito comBoolean()
eString()
.
A conversão implícita é complicada, pois é acionada em mais casos:
- operadores de comparação (comparison operators)(
>
,<
,<=
,>=
) - operadores bitwise (
|&^~
) - operadores aritméticos (
-+*/%
). Saiba que usar+
não irá ativar a conversão numérica quando qualquer operando for uma string. - operador unário
+
- operador de igualdade
==
(incl.!=
).\ Perceba que==
não ativa a conversão numérica quando ambos operandos são strings.
Number('123') // explícito
+'123' // implícito
123 != '456' // implícito
4 > '5' // implícito
5/null // implícito
true | 0 // implícito
Abaixo como valores primitivos são convertido para números:
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(" 12 ") // 12
Number("-12.34") // -12.34
Number("\n") // 0
Number(" 12s ") // NaN
Number(123) // 123
Ao converter uma string em número, a engine primeiro remove os espaços em branco com os caracteres\n
e\t
, retornandoNaN
se a string tratada não representar um número válido. Se a string estiver vazia, retornará0
.
null
eundefined
são tratados de forma diferentes:null
vira 0, enquantoundefined
se tornaNaN
.
Symbols não podem ser convertidos em números nem explicitamente nem implicitamente. Além disse,TypeError
é lançado ao invés de silenciosamente converter paraNaN
, como acontece paraundefined
. Veja mais sobre as regras de conversão de símbolo no MDN.
Number(Symbol('my symbol')) // TypeError é lançado
+Symbol('123') // TypeError é lançado
Existem*duas regras especiais*para relembrar:
- Quando aplicamos
==
paranull
ouundefined
, a conversão numérico não ocorre.null
é apenas igual anull
ouundefined
, e não é igual a mais nada.
null == 0 // false, null is not converted to 0
null == null // true
undefined == undefined // true
null == undefined // true
2.NaN
não é igual a nada que não seja ele mesmo:
if (value !== value) { console.log("we're dealing with NaN here") }
Coerção de tipos para objetos
Até agora, analisamos a coerção de tipos para valores primitivos. Isso não é muito empolgante.
Quando isso ocorre com objetos, e a engine encontra expressões como[1] + [2,3]
, primeiramente será preciso converter o objeto para um valor primitivo, que é então convertido pro tipo final. E ainda assim existem apenas três tipos de conversão: numérico, string e booleano.
O caso mais simples é a conversão para booleano: qualquer valor não primitivo sempre será convertido paratrue
, não importa se um objeto ou array está vazio ou não.
Objetos são convertidos para primitivos através da função[[ToPrimitive]]
, que é responsável pela conversão numérica e string.
Abaixo uma pseudo implementação do método[[ToPrimitive]]
:
function ToPrimitive(input, preferredType){
switch (preferredType){
case Number:
return toNumber(input);
break;
case String:
return toString(input);
break
default:
return toNumber(input);
}
function isPrimitive(value){
return value !== Object(value);
}
function toString(){
if (isPrimitive(input.toString())) return input.toString();
if (isPrimitive(input.valueOf())) return input.valueOf();
throw new TypeError();
}
function toNumber(){
if (isPrimitive(input.valueOf())) return input.valueOf();
if (isPrimitive(input.toString())) return input.toString();
throw new TypeError();
}
}
[[ToPrimitive]]
é invocado passando dois argumentos:
- input: valor a ser convertido;
- preferredType: Tipo escolhido para conversão, podendo ser
Number
ouString
. Esse argumento é opcional.
Ambas conversões, número e string fazem uso de dois métodos do objeto de entrada (input):valueOf
etoString
. Ambas funções são declaradas noObject.prototype
e, portanto, disponível para qualquer tipo derivado, comoDate
,Array
, e etc.
Em geral, o algoritmo é o seguinte:
Se o input já é do tipo primitivo, retorne-o;
Chame a função
input.toString()
, se o resultado for do tipo primitivo, retorne-o;Chame a função
input.valueOf()
, se o resultado for do tipo primitivo, retorne-o;Se nem a função
input.toString()
ouinput.valueOf()
retornar um tipo primitivo, lanceTypeError
.
Conversões numéricas primeiro chamam a funçãovalueOf
(3) com o fallbacktoString
(2).
A conversão de string faz exatamente o oposto:toString
(2) seguido devalueOf
(3).
A maioria dos tipos internos(built-in) não possui a funçãovalueOf
, ou possuivalueOf
retornando o próprio objeto, então é ignorado por não ser do tipo primitivo. É por isso que a conversão de tiposnumber
estring
podem funcionar da mesma forma — ambos acabam chamandotoString()
.
Operadores diferentes podem acionar a conversão numérica ou de string com a ajuda do parâmetropreferredType
. Mas existem duas exceções: o comparador de igualdade abstrato==
e a opção binária+
acionam modos de conversão padrão (preferredType
não é especificado, ou igual adefault
). Nesse caso, a maior dos tipos internos(built-in) assumirão uma conversão numérica como default, excetoDate
que fará uma conversão de string.
Segue abaixo um exemplo de como se comporta uma conversa deDate
:
let d = new Date();
// obtém a representação em string
let str = d.toString(); // 'Wed Jan 17 2018 16:15:42'
// obtém a representação numérica, número em milisegundos desde a época do Unix
let num = d.valueOf(); // 1516198542525
// comparara com uma representação de string
// é true, pois "d" foi convertido para a mesma string
console.log(d == str); // true
// compara com a representação numérica
// false, pois d não foi convertido para um número usando valueOf()
console.log(d == num); // false
// O resulado é 'Wed Jan 17 2018 16:15:42Wed Jan 17 2018 16:15:42'
// '+' funcional igual ao '==', aciona o modo padrão de conversão
console.log(d + d);
// O resultado é 0, pois o operador '-' aciona explicitamente a conversão numérica, não a padrão
console.log(d - d);
Você pode sobrescrever os métodos padrãotoString()
evalueOf()
para conectar-se à lógica de conversão objeto para primitivo(object-to-primitive).
var obj = {
prop: 101,
toString(){
return 'Prop: ' + this.prop;
},
valueOf() {
return this.prop;
}
};
console.log(String(obj)); // 'Prop: 101'
console.log(obj + '') // '101'
console.log(+obj); // 101
console.log(obj > 100); // true
Observe comoobj + ‘’
retorna'101'
como uma string. O operador+
dispara um modo de conversão padrão, e como dito anteriormente,Object
assume a conversão numérico como padrão, usando portanto, o métodovalueOf()
ao invés dotoString()
.
Método do ES6 - Symbol.toPrimitive
No ES5 você pode conectar a lógica de conversão de objeto a primitivo(object-to-primitive) substituindo os métodostoString
evalueOf
.
No ES6 você pode ir mais longe, podendo substituir completamente a rotina interna[[ToPrimitive]]
implementando o método[Symbol.toPrimtive]
em um objeto.
class Disk {
constructor(capacity){
this.capacity = capacity;
}
[Symbol.toPrimitive](hint){
switch (hint) {
case 'string':
return 'Capacity: ' + this.capacity + ' bytes';
case 'number':
// convert to KiB
return this.capacity / 1024;
default:
// assume numeric conversion as a default
return this.capacity / 1024;
}
}
}
// 1MiB disk
let disk = new Disk(1024 * 1024);
console.log(String(disk)) // Capacity: 1048576 bytes
console.log(disk + '') // '1024'
console.log(+disk); // 1024
console.log(disk > 1000); // true
Exemplos
Sabendo a teoria, agora vamos aos exemplos:
true + false // 1
12 / "6" // 2
"number" + 15 + 3 // 'number153'
15 + 3 + "number" // '18number'
[1] > null // true
"foo" + + "bar" // 'fooNaN'
'true' == true // false
false == 'false' // false
null == '' // false
!!"false" == !!"true" // true
['x'] == 'x' // true
[] + null + 1 // 'null1'
[1,2,3] == [1,2,3] // false
{}+[]+{}+[1] // '0[object Object]1'
!+[]+[]+![] // 'truefalse'
new Date(0) - 0 // 0
new Date(0) + 0 // 'Thu Jan 01 1970 02:00:00(EET)0'
Abaixo, você encontrará explicações para cada expressão.
O operador binário+
aciona a conversão numérica gerando o resultadotrue
oufalse
.
true + false
==> 1 + 0
==> 1
O operador aritmético/
aciona a conversão numérico para a string'6'
:
12 / '6'
==> 12 / 6
==>> 2
O operador+
possui uma associação de leitura a partir da esquerda para a direita (left-to-right associativity), portanto a expressão"number" + 15
é executada primeiro. Desde que o primeiro operando é uma string, o operador+
aciona a conversão para string do número 15. No segundo passo, a expressão"number15" + 3
é tratada da mesma forma.
"number" + 15 + 3
==> "number15" + 3
==> "number153"
A expressão15 + 3
é avaliada primeiro. Já que ambos operandos são numéricos, não é preciso fazer a coerção dos tipos. Mas na segunda expressão, quando18 + 'number'
é avalido, ao verificar que um dos operandos é uma string, ele aciona a conversão para string.
15 + 3 + "number"
==> 18 + "number"
==> "18number"
O operador de comparação>
acionada a conversão numérica para[1]
enull
.
[1] > null
==> '1' > 0
==> 1 > 0
==> true
O operador unário+
tem maior precedência ao operador binário+
. Então a expressão+'bar'
é avaliada primeiro. O operador unário aciona a conversão numérica para a string'bar'
. Já que a string não apresenta um número válido, o resultado seráNaN
. Na segunda etapa, a expressão'foo' + NaN
será avaliada.
"foo" + + "bar"
==> "foo" + (+"bar")
==> "foo" + NaN
==> "fooNaN"
O operador==
aciona a conversão numérica, a stringtrue
é convertida paraNaN
, o booleanotrue
é convertido para 1.
'true' == true
==> NaN == 1
==> false
false == 'false'
==> 0 == NaN
==> false
O operador==
normalmente aciona a conversão numérica, mas não é o caso quando é colocadonull
.null
é igual apenas anull
ouundefined
.
null == ''
==> false
O operador!!
converter ambas strings'true'
e'false'
para o booleanotrue
, já que eles não são strings vazias. Então,==
apenas verifica a igualdade de dois booleanostrue
sem qualquer coerção.
!!"false" == !!"true"
==> true == true
==> true
O operador==
aciona a conversão numérica para um array. O método do arrayvalueOf()
retorna o próprio array, e é ignorado por não ser um primitivo. A função do arraytoString()
converte['x']
para a string'x'
.
['x'] == 'x'
==> 'x' == 'x'
==> true
O operador+
aciona uma conversão numérica para[]
. A função do arrayvalueOf()
é ignorado, pois retorna a si mesmo, cujo valor não é primitivo. A função do arraytoString()
retorna uma string vazia.
Na segunda expressão'' + null + 1
é avaliada.
[] + null + 1
==> '' + null + 1
==> 'null' + 1
==> 'null1'
Os operadores lógicos||
e&&
fazem coerção para booleano, mas retornando os operandos originais — não valores booleanos.0
é falso(falsy), enquanto'0'
é verdadeiro(truthy), pois não é uma string vazia. Um objeto vazio{}
também retorna verdadeiro(truthy).
0 || "0" && {}
==> (0 || "0") && {}
==> (false || true) && true // internamente
==> "0" && {}
==> true && true // internamente
==> {}
Não é preciso fazer coerção pois ambos operandos são do mesmo tipo. Desde que==
verifica a identidade do objeto (object identity), e não sua igualdade (object equality), o resultado seráfalse
, por conta dos 2 arrays serem de instâncias diferentes.
[1,2,3] == [1,2,3]
==> false
Todos os operandos são valores não primitivos, portanto,+
inicia a conversão numérica com o item mais a esquerda. A funçãovalueOf
de ambos objetos e arrays retornarão a si mesmo, e serão ignorados. O métodotoString()
é usado como fallback. A pegadinha aqui é que{}
não é considerado um objeto literal, mas sim como um bloco de declaração de estado, então é ignorado. A avaliação começará com a próxima expressão+ []
, que será convertido para uma string vazia através do métodotoString()
, e então para0
.
{}+[]+{}+[1]
==> +[]+{}+[1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + [1]
==> '0[object Object]' + [1]
==> '0[object Object]' + '1'
==> '0[object Object]1'
Esse é mais fácil de explicar, pois o passo a passo de sua resolução se dará de acordo com a precedência do operador.
!+[]+[]+![]
==> (!+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'
O operador-
acionará a conversão numérica paraDate
. A funçãoDate.valueOf()
retornará o número de milissegundos desde a época do Unix.
new Date(0) - 0
==> 0 - 0
==> 0
O operador+
acionará a conversão padrão.Date
assumirá uma conversão para string, portanto o métodotoString()
será utilizado, ao invés dovalueOf()
.
new Date(0) + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'
Rápidas explicações
O que é um operador unário e binário?
- Unário: aquele que interage sobre um elemento. Ex: +, -, ++.
- Binário: aquele que interage sobre dois elementos. Ex: +, -, *, /, &, &&.
Referências
Recomendo o excelente livro“Understanding ES6” escrito porNicholas C. Zakas. É uma grande fonte para aprender ES6, não é tão avançado, e não fica muito tempo em partes mais profundas.
E aqui um ótimo livro de ES5 —SpeakingJSwritten porAxel Rauschmayer.
(Russian) Современный учебник Javascript —https://learn.javascript.ru/. Especiallythesetwopages on type coercion.
JavaScript Comparison Table —https://dorey.github.io/JavaScript-Equality-Table/
wtfjs — a little code blog about that language we love despite giving us so much to hate —https://wtfjs.com/
https://codeburst.io/javascript-quickie-what-is-type-coercion-74f19df6d16f
https://medium.com/codezillas/let-me-coerce-you-into-liking-javascripts-dynamic-typing-system-3cd22c19cb64
https://medium.com/intrinsic/javascript-object-type-coercion-b2ec176c02c4
https://hackernoon.com/understanding-js-coercion-ff5684475bfc
Top comments (0)