Usar == no JavaScript já faz sentido?

276

Em JavaScript, The Good Parts , Douglas Crockford escreveu:

O JavaScript possui dois conjuntos de operadores de igualdade: ===e !==, e seus gêmeos maus ==e !=. Os bons funcionam da maneira que você esperaria. Se os dois operandos forem do mesmo tipo e tiverem o mesmo valor, ===produz truee !==produz false. Os gêmeos maus fazem a coisa certa quando os operandos são do mesmo tipo, mas se são de tipos diferentes, tentam coagir os valores. As regras pelas quais eles fazem isso são complicadas e imemoráveis. Estes são alguns dos casos interessantes:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

A falta de transitividade é alarmante. Meu conselho é nunca usar os gêmeos do mal. Em vez disso, sempre use ===e !==. Todas as comparações mostradas são produzidas falsecom o ===operador.

Dada essa observação inequívoca, existe um momento em que o uso ==pode ser realmente apropriado?

Robert Harvey
fonte
11
Faz sentido em muitos lugares. Sempre que é óbvio que você está comparando duas coisas do mesmo tipo (isso acontece muito na minha experiência), == vs === apenas se resume à preferência. Outras vezes, você realmente deseja uma comparação abstrata (como o caso mencionado na sua resposta). A adequação depende das convenções para qualquer projeto.
Ei
4
Em relação a "muitos lugares", na minha experiência, os casos em que não importa superam os casos em que importa. Sua experiência pode ser diferente; talvez tenhamos experiência com diferentes tipos de projetos. Quando olho para projetos que usam ==por padrão, ===se destaca e me informa que algo importante está acontecendo.
Ei
4
Eu não acho que o JavaScript vá longe o suficiente com sua coerção de tipo. Deveria ter ainda mais opções de coerção de tipos, assim como a linguagem BS .
Mark Booth
5
Um lugar que eu uso == é ao comparar os IDs suspensos (que são sempre caracteres) com os IDs do modelo (que geralmente são int).
21715
12
O desenvolvimento da Web @DevSolar faz sentido quando você não precisa lidar com a produção de um aplicativo nativo para cada uma das 15 plataformas, além da certificação na App Store monopólio de cada plataforma que possui uma.
precisa saber é o seguinte

Respostas:

232

Eu vou fazer um argumento para ==

Douglas Crockford, que você citou, é conhecido por suas muitas e muitas vezes muito úteis opiniões. Enquanto estou com Crockford, nesse caso em particular, vale a pena mencionar que não é a única opinião. Existem outros, como o criador da linguagem Brendan Eich, que não vêem o grande problema ==. O argumento é mais ou menos o seguinte:

JavaScript é uma linguagem de tipo comportamental *. As coisas são tratadas com base no que elas podem fazer e não no seu tipo real. É por isso que você pode chamar o .mapmétodo de uma matriz em um NodeList ou em um conjunto de seleção jQuery. É também por isso que você pode fazer 3 - "5"e obter algo significativo de volta - porque "5" pode agir como um número.

Ao executar uma ==igualdade, você está comparando o conteúdo de uma variável em vez de seu tipo . Aqui estão alguns casos em que isso é útil:

  • Lendo um número do usuário - leia o .valuede um elemento de entrada no DOM? Sem problemas! Você não precisa começar a transmiti-lo ou se preocupar com o seu tipo - você pode ==imediatamente obter números e obter algo significativo de volta.
  • Precisa verificar a "existência" de uma variável declarada? - você pode == null, uma vez que nullrepresenta comportamentalmente não há nada lá e indefinido também não tem nada lá.
  • Precisa verificar se você recebeu informações significativas de um usuário? - verifique se a entrada é falsa com o ==argumento, ele tratará os casos em que o usuário não inseriu nada ou apenas espaço em branco para você, o que provavelmente é o que você precisa.

Vamos dar uma olhada nos exemplos de Crockford e explicá-los comportamentalmente:

'' == '0'           // got input from user vs. didn't get input - so false
0 == ''             // number representing empty and string representing empty - so true
0 == '0'            // these both behave as the number 0 when added to numbers - so true    
false == 'false'    // false vs got input from user which is truthy - so false
false == '0'        // both can substitute for 0 as numbers - so again true

false == undefined  // having nothing is not the same as having a false value - so false
false == null       // having empty is not the same as having a false value - so false
null == undefined   // both don't represent a value - so true

' \t\r\n ' == 0     // didn't get meaningful input from user vs falsey number - true 

Basicamente, ele ==foi projetado para funcionar com base em como as primitivas se comportam no JavaScript, não com base no que são . Embora eu pessoalmente não concorde com esse ponto de vista, definitivamente há mérito em fazê-lo - especialmente se você adotar esse paradigma de tratamento de tipos com base no comportamento em toda a linguagem.

* alguns podem preferir o nome de tipagem estrutural, que é mais comum, mas há uma diferença - não está realmente interessado em discutir a diferença aqui.

Benjamin Gruenbaum
fonte
8
Esta é uma ótima resposta, e eu uso todos os três dos seus casos de uso 'for =='. # 1 e # 3 são especialmente úteis.
Chris Cirefice
224
O problema ==não é que nenhuma de suas comparações seja útil , é que as regras são impossíveis de serem lembradas, portanto você quase cometerá erros. Por exemplo: "Precisa verificar se você recebeu uma entrada significativa de um usuário?", Mas '0' é uma entrada significativa e '0'==falseverdadeira. Se você tivesse usado, ===teria que pensar explicitamente sobre isso e não teria cometido o erro.
Timmmm 6/01/15
44
"é impossível lembrar as regras" <== essa é a única coisa que me assusta em fazer algo "significativo" em Javascript. (e flutuar matemática que levar a problemas em exercícios básicos Calc)
WernerCD
9
O 3 - "5"exemplo levanta um bom ponto por si só: mesmo se você usar exclusivamente ===para comparação, é assim que as variáveis ​​funcionam em Javascript. Não há como escapar completamente.
Jarett Millard
23
@ Timimm note Estou jogando como advogado do diabo aqui. Eu não uso ==no meu próprio código, acho um anti-padrão e concordo completamente que o algoritmo de igualdade abstrata é difícil de lembrar. O que estou fazendo aqui é argumentar com as pessoas.
Benjamin Gruenbaum
95

Acontece que o jQuery usa a construção

if (someObj == null) {
  // do something
}

extensivamente, como uma abreviação para o código equivalente:

if ((someObj === undefined) || (someObj === null))  {
  // do something
}

Isso é uma conseqüência da Especificação de Linguagem ECMAScript § 11.9.3, O Algoritmo de Comparação de Igualdade Abstrata , que declara, entre outras coisas, que

1.  If Type(x) is the same as Type(y), then  
    a.  If Type(x) is Undefined, return true.  
    b.  If Type(x) is Null, return true.

e

2.  If x is null and y is undefined, return true.
3.  If x is undefined and y is null, return true.

Essa técnica específica é comum o suficiente para que o JSHint tenha um sinalizador projetado especificamente para ela.

Robert Harvey
fonte
10
Não é justo responder à sua própria pergunta, eu queria responder a isso :) == null or undefinedé o único lugar onde eu não uso ===ou!==
pllee
26
Para ser justo, o jQuery dificilmente é uma base de código modelo. Depois de ler a fonte jQuery várias vezes, é uma das minhas bases de código menos favoritas, com muitos ternários aninhados, bits pouco claros, aninhamento e coisas que, de outra forma, eu evitaria em código real. Mas não aceite minha palavra - leia github.com/jquery/jquery/tree/master/src e compare-a com o Zepto, que é um clone do jQuery: github.com/madrobby/zepto/tree/master/src
Benjamin Gruenbaum
4
Observe também que o Zepto parece usar como padrão ==e apenas é usado ===nos casos em que é necessário: github.com/madrobby/zepto/blob/master/src/event.js
Hey
2
@ Para ser justo, o Zepto também não é uma base de código de modelos - é famoso por usar __proto__e, por sua vez, forçá-lo quase que sozinho na especificação da linguagem para evitar a quebra de sites móveis.
Benjamin Gruenbaum
2
@BenjaminGruenbaum que não julgou a qualidade de sua base de código, apenas salientando que projetos diferentes seguem convenções diferentes.
Ei
15

Verificar valores para nullou undefinedé uma coisa, como foi explicado em abundância.

Há outra coisa, onde ==brilha:

Você pode definir a comparação da seguinte >=forma (as pessoas geralmente começam, >mas acho isso mais elegante):

  • a > b <=> a >= b && !(b >= a)
  • a == b <=> a >= b && b >= a
  • a < be a <= bsão deixados como um exercício para o leitor.

Como sabemos, em JavaScript "3" >= 3e "3" <= 3de onde você obtém 3 == "3". Você pode dizer que é uma péssima idéia permitir a comparação de comparação entre seqüências e números analisando a sequência. Mas, como é assim que funciona, ==é absolutamente a maneira correta de implementar esse operador de relacionamento.

Portanto, a coisa realmente boa ==é que é consistente com todos os outros relacionamentos. Em outras palavras, se você escrever isso:

function compare(a, b) {
  if (a > b) return 1;
  if (a < b) return -1;
  return 0;
}

Você ==já está usando implicitamente .

Agora, para a questão bastante relacionada: Foi uma má escolha implementar a comparação de números e cadeias de caracteres da maneira como é implementada? Visto isoladamente, parece uma coisa estúpida de se fazer. Mas no contexto de outras partes do JavaScript e do DOM, é relativamente pragmático, considerando o seguinte:

  • atributos são sempre strings
  • As chaves são sempre strings (o caso de uso é que você usa um Objectpara ter um mapa esparso de ints a valores)
  • os valores de entrada do usuário e controle de formulário são sempre cadeias de caracteres (mesmo que a origem corresponda input[type=number])

Por várias razões, fazia sentido que as cordas se comportassem como números quando necessário. E supondo que a comparação e concatenação de cadeias ::possuam operadores diferentes (por exemplo, para concating e um método para comparar (onde você pode usar todos os tipos de parâmetros relacionados à distinção entre maiúsculas e minúsculas e outras)), isso seria realmente uma bagunça. Mas essa sobrecarga de operador provavelmente é de fato a origem do "Java" em "JavaScript";)

back2dos
fonte
4
Ser justo >=não é realmente transitivo. É bem possível no JS que nem a > bnem a < bnem b == a(por exemplo NaN:).
Benjamin Gruenbaum
8
@BenjaminGruenbaum: É como dizer que +não é realmente comutativo, porque NaN + 5 == NaN + 5não se sustenta. O ponto é que >=trabalha com valores numéricos para os quais ==trabalha de maneira consistente. Não deveria ser surpresa que "não é um número" é, por sua própria natureza, não número-ish;)
back2dos
4
Portanto, o mau comportamento de ==é consistente com o mau comportamento de >=? Ótimo, agora eu gostaria que houvesse uma >==...
Eldritch Conundrum
2
@ EldritchConundrum: Como tentei explicar, o comportamento de >=é bastante consistente com o restante das APIs de linguagem / padrão. Na sua totalidade, o JavaScript consegue ser mais do que a soma de suas partes peculiares. Se você gostaria de um >==, você também gostaria de um rigoroso +? Na prática, muitas dessas decisões facilitam muitas coisas. Então eu não me apressaria em julgá-los como pobres.
back2dos
2
@EldritchConundrum: Novamente: os operadores de relacionamento devem comparar valores numéricos, onde um operando pode de fato ser uma string. Para tipos de operando para os quais >=é significativo, ==é igualmente significativo - isso é tudo. Ninguém diz que você deve comparar [[]]com false. Em linguagens como C, o resultado desse nível de absurdo é um comportamento indefinido. Apenas trate da mesma maneira: não faça. E você ficará bem. Você também não precisará se lembrar de nenhuma regra mágica. E então é realmente bastante direto.
back2dos
8

Como matemático profissional, vejo no operador de uniformidade de Javscript == (também chamado de "comparação abstrata", "igualdade frouxa" ) uma tentativa de construir uma relação de equivalência entre entidades, que inclui ser reflexiva , simétrica e transitiva . Infelizmente, duas dessas três propriedades fundamentais falham:

==não é reflexivo :

A == A pode ser falso, por exemplo

NaN == NaN // false

==não é transitivo :

A == Be B == Cjuntos não implicam A == C, por exemplo,

'1' == 1 // true
1 == '01' // true
'1' == '01' // false

Somente a propriedade simétrica sobrevive:

A == Bimplica B == A, que violação é provavelmente impensável em qualquer caso e levaria a uma rebelião séria;)

Por que as relações de equivalência são importantes?

Porque esse é o tipo de relação mais importante e predominante, suportado por vários exemplos e aplicativos. A aplicação mais importante é a decomposição de entidades em classes de equivalência , que por si só é uma maneira muito conveniente e intuitiva de entender as relações. E a falta de equivalência leva à falta de classes de equivalência, o que, por sua vez, leva à falta de intuitividade e complexidade desnecessária que é bem conhecida.

Por que é uma péssima idéia escrever ==para uma relação de não equivalência?

Porque rompe nossa familiaridade e intuição, pois literalmente qualquer relação interessante de similaridade, igualdade, congruência, isomorfismo, identidade etc. é uma equivalência.

Conversão de tipo

Em vez de confiar em uma equivalência intuitiva, o JavaScript introduz a conversão de tipo:

O operador de igualdade converte os operandos se não forem do mesmo tipo e aplica uma comparação estrita.

Mas como é definida a conversão de tipo? Através de um conjunto de regras complicadas, com inúmeras exceções?

Tentativa de construir relação de equivalência

Booleanos. Claramente truee falsenão são iguais e devem estar em classes diferentes.

Números. Felizmente, a igualdade de números já está bem definida, na qual dois números diferentes nunca estão na mesma classe de equivalência. Em matemática, é isso. Em JavaScript, a noção de número é um pouco deformada pela presença dos mais exóticos -0, Infinitye -Infinity. Nossa intuição matemática determina que 0e -0deve estar na mesma classe (de fato -0 === 0é true), enquanto que cada um dos infinitos é uma classe separada.

Números e booleanos. Dadas as classes numéricas, onde colocamos booleanos? falsetorna-se semelhante a 0, enquanto truetorna-se semelhante a, 1mas nenhum outro número:

true == 1 // true
true == 2 // false

Existe alguma lógica aqui para colocar truejunto com 1? É certo que 1se distingue, mas também é -1. Eu pessoalmente não vejo qualquer razão para converter truepara 1.

E fica ainda pior:

true + 2 // 3
true - 1 // 0

Então, de truefato, é convertido em 1entre todos os números! Isso é lógico? É intuitivo? A resposta é deixada como exercício;)

Mas e quanto a isso:

1 && true // true
2 && true // true

O único booleano xcom o x && trueser trueé x = true. O que prova que ambos 1e 2(e qualquer outro número que 0) sejam convertidos para true! O que mostra é que nossa conversão falha em outra propriedade importante - a bijeção . Significando que duas entidades diferentes podem ser convertidas na mesma. O que, por si só, não precisa ser um grande problema. O grande problema surge quando usamos essa conversão para descrever uma relação de "semelhança" ou "igualdade frouxa" do que queremos chamar. Mas uma coisa é clara - não será uma relação de equivalência e não será descrita intuitivamente por meio de classes de equivalência.

Mas podemos fazer melhor?

Pelo menos matematicamente - definitivamente sim! Uma simples relação de equivalência entre booleanos e números poderia ser construída com apenas falsee 0estar na mesma classe. Assim false == 0seria a única igualdade frouxa não trivial.

E as cordas?

Podemos cortar strings de espaços em branco no início e no final para converter em números, também podemos ignorar zeros na frente:

'   000 ' == 0 // true
'   0010 ' == 10 // true

Portanto, temos uma regra simples para uma string - apare os espaços em branco e os zeros na frente. Ou obtemos um número ou uma sequência vazia; nesse caso, convertemos para esse número ou zero. Ou não obtemos um número; nesse caso, não convertemos e, portanto, não obtemos nova relação.

Dessa forma, poderíamos obter uma relação de equivalência perfeita no conjunto total de booleanos, números e seqüências de caracteres! Exceto que ... Os designers de JavaScript obviamente têm outra opinião:

' ' == '' // false

Portanto, as duas seqüências para as quais os dois convertem 0são subitamente semelhantes! Porque ou porque? De acordo com a regra, as strings são fracamente iguais exatamente quando são estritamente iguais! Não apenas essa regra quebra a transitividade como vemos, mas também é redundante! Qual o sentido de criar outro operador ==para torná-lo estritamente idêntico ao outro ===?

Conclusão

O operador de igualdade frouxa ==poderia ter sido muito útil se estivesse cumprindo algumas leis matemáticas fundamentais. Mas como infelizmente não, sua utilidade sofre.

Dmitri Zaitsev
fonte
Que tal NaN? Além disso, a menos que um formato de número específico seja imposto para comparação com cadeias, deve resultar em comparação não intuitiva de cadeias ou não transitividade.
Solomon Ucko
@SolomonUcko NaNage como um cidadão ruim :-). A transatividade pode e deve ser mantida para qualquer comparação de equivalência, intuitiva ou não.
Dmitri Zaitsev
7

Sim, deparei-me com um caso de uso, ou seja, quando você está comparando uma chave com um valor numérico:

for (var key in obj) {
    var some_number = foo(key, obj[key]);  // or whatever -- this is just an example
    if (key == some_number) {
        blah();
    }
}

Eu acho que é muito mais natural realizar a comparação key == some_numberdo que como Number(key) === some_numberou como key === String(some_number).

Mehrdad
fonte
3

Encontrei um aplicativo bastante útil hoje. Se você deseja comparar números preenchidos, como 01números inteiros normais, ==funciona muito bem. Por exemplo:

'01' == 1 // true
'02' == 1 // false

Isso poupa a remoção do 0 e a conversão para um número inteiro.

Jon Snow
fonte
4
Eu tenho certeza que a maneira 'certa' de fazer isso é '04'-0 === 4, ou possivelmenteparseInt('04', 10) === 4
ratbum
Eu não sabia que você poderia fazer isso.
Jon Snow
7
Eu recebo muito isso.
Jon Snow
1
@ratbum ou+'01' === 1
Eric Lagergren
1
'011' == 011 // falseno modo não estrito e SyntaxError no modo estrito. :)
Brian S
3

Eu sei que esta é uma resposta tardia, mas parece haver alguma confusão possível sobre nulle undefined, qual IMHO é o que faz o ==mal, mais ainda que a falta de transitividade, que já é ruim o suficiente. Considerar:

p1.supervisor = 'Alice';
p2.supervisor = 'None';
p3.supervisor = null;
p4.supervisor = undefined;

O que isso significa?

  • p1 tem um supervisor cujo nome é "Alice".
  • p2 tem um supervisor cujo nome é "Nenhum".
  • p3explicitamente, inequivocamente, não possui um supervisor .
  • p4pode ou pode ter um supervisor. Não sabemos, não nos importamos, não devemos saber (questão de privacidade?), Pois não é da nossa conta.

Quando você usa, ==está em conflito nulle o undefinedque é totalmente impróprio. Os dois termos significam coisas completamente diferentes! Dizer que não tenho um supervisor simplesmente porque me recusei a dizer quem é meu supervisor está errado!

Entendo que existem programadores que não se importam com essa diferença entre nulle / undefinedou optam por usar esses termos de maneira diferente. E se o seu mundo não usa nulle undefinedcorretamente, ou você deseja dar sua própria interpretação com esses termos, que assim seja. Acho que não é uma boa ideia.

Agora, a propósito, não tenho nenhum problema nulle undefinedambos são falsos! É perfeitamente bom dizer

if (p.supervisor) { ... }

e, em seguida, nulle undefinedfaria com que o código que processa a supervisor para ser ignorada. Isso está correto, porque não sabemos ou não temos um supervisor. Tudo bom. Mas as duas situações não são iguais . É por isso que ==está errado. Novamente, as coisas podem ser falsas e usadas no sentido de digitação de pato, o que é ótimo para linguagens dinâmicas. É JavaScript, Pythonic, Rubyish etc. adequados. Mas, novamente, essas coisas NÃO são iguais.

E não me fale sobre a não-transitividade: "0x16" == 10, 10 == "10"mas não "10" == "0x16". Sim, o JavaScript é um tipo fraco. Sim, é coercitivo. Mas a coerção nunca deve se aplicar à igualdade.

A propósito, Crockford tem opiniões fortes. Mas você sabe o que? Ele está correto aqui!

FWIW Entendo que existem e, pessoalmente, me deparei com situações em que ==é conveniente! Como pegar entrada de string para números e, digamos, comparar com 0. No entanto, isso é hack. Você tem a conveniência de compensar um modelo impreciso do mundo.

TL; DR: falsidade é um ótimo conceito. Não deve se estender à igualdade.

Ray Toal
fonte
Obrigado por mostrar as diferentes situações :) No entanto, você está faltando p5... a situação de typeof(p5.supervisor) === typeof(undefined)onde supervisor nem sequer existe como um conceito: D
TheCatWhisperer