A ES6 possui três facilidades internas para determinar se algum x e algum y são "os mesmos". Elas são: igualdade ou "igual duplo" (==
), igualdade rigorosa ou "igual triplo" (===
), e Object.is
. (Note que Object.is
foi adicionado na ES6. Ambos igual duplo e igual triplo existiam antes da ES6, e seu comportamento permanece o mesmo.)
Visão geral
Para demonstração, aqui estão as três comparações de igualdade em uso:
x == y
x === y
Object.is(x, y)
De modo breve, o operador igual duplo irá realizar uma conversão de tipo na hora de comparar duas coisas; o operador igual triplo fará a mesma comparação sem conversão de tipo (simplesmente sempre retornando false
se os tipos forem diferentes); e Object.is
se comportará da mesma forma que o operador igual triplo, mas lidando de forma especial com NaN
e -0
e +0
de modo que os dois últimos não são considerados os mesmos, enquanto que Object.is(NaN, NaN)
será true
. (Comparar NaN
com NaN
geralmente—i.e., usando-se o operador igual duplo ou o operador igual triplo—resulta em false
, de acordo com a IEEE 754.)
Note que a distinção entre todas essas formas de comparação tem a ver com a forma com que lidam com primitivos; nenhuma delas compara se os parâmetros são conceitualmente similares em estrutura. Para quaisquer objetos não-primitivos x e y que têm a mesma estrutura mas que são objetos distintos, todas as formas de comparação acima resultarão no valor false
.
Por exemplo:
let x = { value: 17 }; let y = { value: 17 }; console.log(Object.is(x, y)); // false; console.log(x === y); // false console.log(x == y); // false
Igualdade abstrata, igualdade rigorosa, e mesmo valor
Na ES5, a comparação realizada por ==
é descrita na Seção 11.9.3, O Algoritmo de Igualdade Abstrata. A comparação ===
é descrita em 11.9.6, O Algoritmo de Igualdade Rigorosa. (Dê uma olhada nestes. Eles são rápidos e legíveis. Dica: leia o algoritmo de igualdade rigorosa primeiro.) A ES5 também descreve, na Seção 9.12, O Algoritmo de MesmoValor para ser usado internamente pelo engine JS. Ele é em sua maioria similar ao Algoritmo de Igualdade Rigorosa, com exceção de que 11.9.6.4 e 9.12.4 diferem na forma de lidar com Number
s (números). A ES6 simplesmente propõe expôr esse algoritmo através de Object.is
.
Podemos ver que com os operadores igual duplo e igual triplo, com a exceção de fazer uma checagem de tipo de início em 11.9.6.1, o Algoritmo de Igualdade Rigorosa é um subconjunto do Algoritmo de Igualdade Abstrata, pois 11.9.6.2–7 corresponde a 11.9.3.1.a–f.
Um modelo para entender comparações de igualdade?
Antes da ES6, você pode ter dito, a respeito dos operadores igual duplo e igual triplo, que um é uma versão "melhorada" do outro. Por exemplo, alguém poderia dizer que o operador igual duplo é uma versão extendidad do operador igual triplo, pois o primeiro faz tudo que o último faz, com conversão de tipo em seus operandos (por exemplo, de modo que 6 == "6"
). Alternativamente, alguém poderia dizer que o operador igual triplo é uma versão melhorada do operador igual duplo, pois requer que os dois operandos sejam do mesmo tipo. Qual é melhor depende de qual é a sua idéia de patamar.
No entanto, essa forma de pensar sobre os operados de igualdade internos não é um modelo que pode ser "esticado" para permitir um lugar para o Object.is
da ES6 nesse "espectro". O Object.is
não é simplesmente "menos restrito" do que o operador igual duplo ou "mais restrito" do que o operador igual triplo, nem cabe em algum lugar entre esses níveis (isto é, sendo mais restrito que o operador igual duplo, mas menos restrito que o operador igual triplo). Nós podemos ver da tabela de comparações de igualdade abaixo que isto é devido à forma com que Object.is
lida com NaN
. Perceba que se Object.is(NaN, NaN)
resultasse no valor false
, nós poderíamos dizer que ele cabe no espectro pouco restrito/restrito como uma forma ainda mais restrita do operador igual triplo, uma que faz distinção entre -0
e +0
. A forma de lidar com NaN
significa que isso não é verdade, no entanto. Infelizmente, Object.is
simplesmente deve ser considerado em termos de duas características específicas, ao invés de sua rigorosidade (ou falta da mesma) com respeito aos operadores de igualdade.
x | y | == |
=== |
Object.is |
---|---|---|---|---|
undefined |
undefined |
true |
true |
true |
null |
null |
true |
true |
true |
true |
true |
true |
true |
true |
false |
false |
true |
true |
true |
"foo" |
"foo" |
true |
true |
true |
{ foo: "bar" } |
x |
true |
true |
true |
0 |
0 |
true |
true |
true |
+0 |
-0 |
true |
true |
false |
0 |
false |
true |
false |
false |
"" |
false |
true |
false |
false |
"" |
0 |
true |
false |
false |
"0" |
0 |
true |
false |
false |
"17" |
17 |
true |
false |
false |
new String("foo") |
"foo" |
true |
false |
false |
null |
undefined |
true |
false |
false |
null |
false |
false |
false |
false |
undefined |
false |
false |
false |
false |
{ foo: "bar" } |
{ foo: "bar" } |
false |
false |
false |
new String("foo") |
new String("foo") |
false |
false |
false |
0 |
null |
false |
false |
false |
0 |
NaN |
false |
false |
false |
"foo" |
NaN |
false |
false |
false |
NaN |
NaN |
false |
false |
true |
Quando usar Object.is
versus o operador igual triplo
Além da forma com que trata o valor it NaN
, de modo geral, o único caso em o comportamento especial de Object.is
com a relação a zeros é capaz de ser de interesse é na busca de certos esquemas de metaprogramação, especialmente em relação a descritores de propriedade quando é desejável que seu trabalho espelhe algumas das características de Object.defineProperty
. Se seu caso de uso não requer isso, sugere-se evitar-se Object.is
e usar-se o operador ===
ao invés disso. Mesmo se seus requerimentos envolvem que comparações entre dois valores NaN
resultem em true
, de modo geral é mais fácil fazer-se uma checagem especial por NaN
s (usando-se o método isNaN
disponíveis de versões anteritores da ECMAScript) do que lidar com como computações externas possam afetar o sinal de quaisquer zeros que você possa encontrar em sua comparação.
Aqui está uma lista não-exaustiva de métodos e operadores internos que podem fazer com que uma distinção entre -0
e +0
se manifeste em seu código:
-
É óbvio que negar
0
produz-0
. Mas a abstração de uma expressão pode fazer com o valor-0
apareça de modo despercebido. Por exemplo, considere o seguinte:let stoppingForce = obj.mass * -obj.velocity
Se
obj.velocity
é0
(ou resulta no valor0
), um-0
é introduzido naquele ponto e propaga-se parastoppingForce
.
-
É possível que um
-0
seja introduzido em uma expressão como um valor de retorno desses métodos em alguns casos, mesmo quando nenhum-0
existe como um dos parâmetros. Por exemplo, usando-seMath.pow
para elevar o valor-Infinity
à potência de qualquer expoente negativo ímpar resulta no valor-0
. Veja a documentação para os métodos individuais.
-
É possível obter-se um valor de retorno
-0
desses métodos em alguns casos quando um-0
existe como um dos parâmetros. Por exemplo,Math.min(-0, +0)
resulta no valor-0
. Veja a documentação para os métodos individuais.
-
~
-
<<
-
>>
-
Cada um desses operadores usa o algoritmo ToInt32 internamente. Uma vez que só há uma representação para o valor 0 no tipo inteiro de 32 bits interno, o valor
-0
não irá sobreviver a um arredondamento depois de uma operação inversa. Por exemplo, ambosObject.is(~~(-0), -0)
eObject.is(-0 << 2 >> 2, -0)
resultam no valorfalse
.
Contar com Object.is
quando o sinal de zeros não é levado em consideração pode ser problemático. É claro que quando a intenção é distinguir entre -0
e +0
, ele faz exatamente o que é desejado.gual