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?


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.


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.


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";)


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.


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).


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.


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.