Você não precisa ser um novato em JavaScript para se confundir com isso…
1 if ([0]) {
2 console.log([0] == true); //false
3 console.log(!![0]); //true
4 }
ou com isso…
1 if ("potato") {
2 console.log("potato" == false); //false
3 console.log("potato" == true); //false
4 }
A boa notícia é que existe um
padrão e todos os browsers o seguem. Alguns autores irão te dizer para temer a
coerção e codificar contra ela. Espero te convencer de que a coerção é um recurso
para ser impulsionado (ou pelo menos compreendido), e não evitado…
O x é verdadeiro? O x
é igual ao y? Perguntas sobre veracidade e igualdade no núcleo das três
maiores áreas do JavaScript: instruções condicionais e operadores (if,
ternaries, &&, || etc.), o operador de igualdade (==), e o operador
de identidade (===). Vejamos o que acontece em cada caso:
Condicionais
No JavaScript, todos as
instruções condicionais e operadores seguem o mesmo paradigma de coerção.
Iremos utilizar o statement if como exemplo.
O construtor if (Expressão) Instruções irá converter o resultado da Expressão para um booleano, usando o
método abstrato ToBoolean, para o qual o spec ES5 define o seguinte algoritmo:
Tipo de Argumento | Resultado |
Indefinido | falso. |
Nulo | falso. |
Boolean |
O resultado é igual ao argumento de entrada (sem conversão). |
Número |
O resultado é falso se o argumento é +0, ?0, ou NaN; caso contrário, o resultado é verdadeiro. |
String |
O resultado é falso se o argumento estiver vazio: String (seu comprimento é zero); caso contrário, o resultado é verdadeiro. |
Objeto | verdadeiro. |
Essa é a fórmula que o JavaScript
usa para classificar valores como verdadeiros
(true, “potato”, 36, [1,2,4] e {a:16}) ou falsos (false,
0, “”, null e undefined).
Agora podemos ver porque, no
exemplo introdutório, if ([0]) permite entrada para o bloco subsequente: um array
é um objeto, e todos os objetos coagem para true.
Aqui temos mais
alguns exemplos. Alguns resultados podem ser surpreendentes, mas eles sempre
aderem às simples regras especificadas acima:
01 var trutheyTester = function(expr) {
02 return expr ? "truthey" : "falsey";
03 }
04
05 trutheyTester({}); //truthey (an object is always true)
06
07 trutheyTester(false); //falsey
08 trutheyTester(new Boolean(false)); //truthey (an object!)
09
10 trutheyTester(""); //falsey
11 trutheyTester(new String("")); //truthey (an object!)
12
13 trutheyTester(NaN); //falsey
14 trutheyTester(new Number(NaN)); //truthey (an object!)
O operador
de igualdade (==)
A versão == de igualdade é
bastante liberal. Os valores podem ser considerados equivalentes mesmo se forem
de tipos diferentes, uma vez que o operador irá forçar a coerção de um ou de ambos
operadores em um único tipo (geralmente um número) antes de fazer a comparação.
Muitos desenvolvedores acham isso um pouco assustador, com certeza estimulados
por pelo menos um guru de JavaScript conhecido que recomenda evitar o operador
== completamente.
A estratégia de evitá-lo me
incomoda, porque você não pode dominar uma linguagem até que a conheça de
dentro para fora – e medo e evasão são inimigos do conhecimento. Além disso,
fingir que o == não existe não vai te salvar quando você tiver que conhecer a
coerção, porque, no JavaScript, a coerção está em todo lugar! Está nas
instruções condicionais (como vimos), na indexação de arrays, em concatenação,
e mais. A coerção, quando usada seguramente, pode ser um instrumento para
escrever um código conciso, elegante e legível.
De qualquer maneira, vamos
dar uma olhada na maneira que o ECMA define como o == funciona. Não é muito
intimidante. Apenas se lembre de que undefined e null se equivalem (e nada mais)
e a maioria dos outros tipos são coagidos a um número para facilitar a
comparação:
.
Tipo(x) | Tipo(y) | Resultado |
x e y são do mesmo tipo | Veja o algoritmo de identidade (===) | |
Nulo | indefinido | verdadeiro |
Indefinido | nulo | verdadeiro |
Número | String | x == toNumber(y) |
String | Número | toNumber(x) == y |
Boolean | (qualquer) | toNumber(x) == y |
(qualquer) | Boolean | x == toNumber(y) |
String ou Number | Object | x == toPrimitive(y) |
Objeto | String ou Number | toPrimitive(x) == y |
Caso contrário… |
falso (resultado) |
Onde o resultado for uma
expressão, o algoritmo é reaplicado até que o resultado seja um boolean. toNumber e toPrimitive
são métodos internos que convertem seus argumentos de acordo com as regras a
seguir:
ToNumber | |
Tipo de Argumento | Resultado |
Indefinido | NaN |
Nulo | +0 |
Boolean |
O resultado é 1 se o argumento for verdadeiro. O resultado é +0 se o argumento for falso. |
Número | O resultado equivale ao argumento de entrada (sem conversão). |
String |
Em effect evaluates Number (string) “abc” -> NaN “123? -> 123 |
Objeto |
Aplique os passos a seguir: 1. Faça primValue receber o valor de ToPrimitive (argumento de entrada, hint Number). |
ToPrimitive | |
Tipo de Argumento | Resultado |
Objeto |
(no caso de o operador de igualdade da coerção) se valueOf retornar um primitivo, o retorne. Caso contrário, se toString retornar um primitivo, o retorne. Caso contrário, jogue um erro. |
Caso contrário | O resultado equivale ao argumento de entrada (sem conversão). |
Aqui estão alguns
exemplos – usarei um pseudo-código para demonstrar passo a passo como o algoritmo
de coerção é aplicado:
[0] ==
true;
01 //EQUALITY CHECK...
02 [0] == true;
03
04 //HOW IT WORKS...
05 //convert boolean using toNumber
06 [0] == 1;
07 //convert object using toPrimitive
08 //[0].valueOf() is not a primitive so use...
09 //[0].toString() -> "0"
10 "0" == 1;
11 //convert string using toNumber
12 0 == 1; //false!
“potato” == true;
1 //EQUALITY CHECK...
2 "potato" == true;
3
4 //HOW IT WORKS...
5 //convert boolean using toNumber
6 "potato" == 1;
7 //convert string using toNumber
8 NaN == 1; //false!
“potato” == false;
1 //EQUALITY CHECK...
2 "potato" == false;
3
4 //HOW IT WORKS...
5 //convert boolean using toNumber
6 "potato" == 0;
7 //convert string using toNumber
8 NaN == 0; //false!
object com getValue
1 //EQUALITY CHECK...
2 crazyNumeric = new Number(1);
3 crazyNumeric.toString = function() {return "2"};
4 crazyNumeric == 1;
5
6 //HOW IT WORKS...
7 //convert object using toPrimitive
8 //valueOf returns a primitive so use it
9 1 == 1; //true!
object com toString
01 //EQUALITY CHECK...
02 var crazyObj = {
03 toString: function() {return "2"}
04 }
05 crazyObj == 1;
06
07 //HOW IT WORKS...
08 //convert object using toPrimitive
09 //valueOf returns an object so use toString
10 "2" == 1;
11 //convert string using toNumber
12 2 == 1; //false!
Operadores
de identidade (===)
Este é fácil. Se os “operandos”
são de tipos diferentes, a resposta é sempre falso. Se eles são do mesmo tipo,
um teste de igualdade intuitivo é aplicado: identificadores de objetos devem
referenciar o mesmo objeto, strings devem conter conjuntos idênticos de
caracteres, e outros primitivos devem dividir o mesmo valor. NaN, null e undefined nunca irão === outro tipo. Nan não === ele mesmo.
Tipo(x) | Valores | Resultado |
Tipo(x) diferente do Tipo(y) | falso (resultado) | |
Indefined ou Nulo | verdadeiro (resultado) | |
Número | x tem o mesmo valor de y (mas não NaN) | verdadeiro |
String | x e y são caracteres idênticos | verdadeiro |
Boolean | x e y são ambos verdadeiros ou ambos falsos | verdadeiro |
Objeto | x e y referenciam o mesmo objeto | verdadeiro |
Caso contrário | falso (resultado) |
Exemplos comuns de igualdade
1 //unnecessary
2 if (typeof myVar === "function");
3
4 //better
5 if (typeof myVar == "function");
…como typeOf retorna uma string, essa operação irá
sempre comparar duas strings. Portanto,
== é 100% a prova de coerção.
1 //unnecessary
2 var missing = (myVar === undefined || myVar === null);
3
4 //better
5 var missing = (myVar == null);
…null e undefined são == entre si.
Nota: devido ao (pequeno)
risco de que a variável undefined possa ser redefinida, igualar a null é
levemente mais garantido.
1 //unnecessary
2 if (myArray.length === 3) {//..}
3
4 //better
5 if (myArray.length == 3) {//..}
Leitura complementar
Peter van der Zee: JavaScript
coercion tool
Um bom resumo do processo de coerção da igualdade, repleto de tutoriais
automatizados bastante impressionantes
Andrea Giammarchi: JavaScript
Coercion Demystified
ECMA-262 5th Edition
11.9.3 The
Abstract Equality Comparison Algorithm
11.9.6 The
Strict Equality Comparison Algorithm
9.1 toPrimitive
9.2 toBoolean
9.3 toNumber
?
Texto original disponível em http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108