Front End

3 ago, 2011

Veracidade, igualdade e JavaScript

Publicidade

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).
2. Returne ToNumber (primValue).

 

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