L'utilisation de == en JavaScript a-t-elle toujours un sens?

276

Douglas Crockford a écrit dans JavaScript, les bonnes parties :

JavaScript a deux ensembles d'opérateurs d'égalité: ===et !==, et leurs jumeaux maléfiques ==et !=. Les bons fonctionnent comme vous le souhaiteriez. Si les deux opérandes sont du même type et ont la même valeur, alors ===product trueet !==product false. Les jumeaux maléfiques font ce qui est juste quand les opérandes sont du même type, mais s’ils sont de types différents, ils tentent de contraindre les valeurs. Les règles selon lesquelles ils le font sont compliquées et immuables. Voici quelques cas intéressants:

'' == '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

Le manque de transitivité est alarmant. Mon conseil est de ne jamais utiliser les jumeaux maléfiques. Au lieu de cela, utilisez toujours ===et !==. Toutes les comparaisons que nous venons de montrer produisent falseavec l' ===opérateur.

Compte tenu de cette observation sans équivoque, y a-t-il un moment où l'utilisation ==pourrait réellement être appropriée?

Robert Harvey
la source
11
C'est logique dans beaucoup d'endroits. À chaque fois qu'il est évident que vous comparez deux choses du même type (cela se produit souvent selon mon expérience), == vs === dépend simplement des préférences. D'autres fois, vous voulez réellement une comparaison abstraite (comme le cas que vous mentionnez dans votre réponse). Que cela soit approprié ou non dépend des conventions applicables à un projet donné.
Hey
4
En ce qui concerne "beaucoup d'endroits", d'après mon expérience, les cas où cela n'a pas d'importance sont plus nombreux que ceux où cela se produit. Votre expérience peut être différente. peut-être avons-nous de l'expérience dans différents types de projets. Lorsque je regarde les projets qui utilisent ==par défaut, ===se démarque et me laisse savoir que quelque chose d'important se passe.
Hé le
4
Je ne pense pas que JavaScript va assez loin avec sa coercition de type. Il devrait avoir encore plus d'options de coercition de types, tout comme le langage BS .
Mark Booth le
5
Un endroit que j'utilise == est la comparaison des identifiants de menu déroulant (qui sont toujours des caractères) avec des identifiants de modèles (qui sont généralement des entiers).
Scottie
12
Le développement Web de @DevSolar est logique lorsque vous ne souhaitez pas produire une application native pour chacune des 15 plates-formes, ni obtenir une certification sur le monopole App Store de chaque plate-forme qui en possède une.
Damian Yerrick

Réponses:

232

Je vais faire un argument pour ==

Douglas Crockford, que vous avez cité, est connu pour ses opinions nombreuses et souvent très utiles. Bien que je sois avec Crockford dans ce cas particulier, il convient de mentionner que ce n'est pas la seule opinion. Il y en a d'autres, comme le créateur de langue Brendan Eich, qui ne voient pas le gros problème avec ==. L'argument va un peu comme ceci:

JavaScript est un langage typé comportementalement. Les choses sont traitées en fonction de ce qu'elles peuvent faire et non de leur type réel. C'est pourquoi vous pouvez appeler la .mapméthode d' un tableau sur une liste de noeuds ou sur un jeu de sélection jQuery. C'est aussi pourquoi vous pouvez faire 3 - "5"quelque chose de significatif et le récupérer - parce que "5" peut agir comme un nombre.

Lorsque vous effectuez une ==égalité, vous comparez le contenu d'une variable plutôt que son type . Voici quelques cas où cela est utile:

  • Lire un numéro de l'utilisateur - lire le contenu .valued'un élément d'entrée dans le DOM? Aucun problème! Vous n'avez pas à commencer à le diffuser ni à vous soucier de son type - vous pouvez ==tout de suite numéroter et obtenir quelque chose de significatif.
  • Besoin de vérifier "l'existence" d'une variable déclarée? - vous pouvez le == nullfaire puisque le comportement nullreprésente qu'il n'y a rien là-bas et qu'undefined n'y a rien non plus.
  • Besoin de vérifier si vous avez reçu une contribution significative d'un utilisateur? - Vérifiez si l'entrée est fausse avec l' ==argument, il traitera les cas où l'utilisateur n'a rien entré ou juste un espace pour vous, ce qui est probablement ce dont vous avez besoin.

Regardons les exemples de Crockford et expliquons-les de manière comportementale:

'' == '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 

Fondamentalement, il ==est conçu pour fonctionner en fonction du comportement des primitives en JavaScript et non en fonction de ce qu'elles sont . Bien que je ne sois pas personnellement d’accord avec ce point de vue, il est tout à fait justifié de le faire - en particulier si vous prenez ce paradigme de traiter les types en fonction du comportement dans l’ensemble du langage.

* Certains préfèreront peut-être le nom de typage structural qui est plus courant mais il y a une différence - ne pas vraiment intéressé à discuter de la différence ici.

Benjamin Gruenbaum
la source
8
C'est une excellente réponse, et j'utilise tous les trois vos cas d'utilisation 'for =='. # 1 & # 3 sont particulièrement utiles.
Chris Cirefice
224
Le problème, ==c’est qu’aucune de ses comparaisons n’est utile , c’est que les règles sont impossibles à mémoriser, de sorte que vous êtes presque certain de faire des erreurs. Par exemple: "Besoin de vérifier si vous avez obtenu une entrée significative de la part d'un utilisateur?", Mais "0" est une entrée significative et '0'==falsevraie. Si vous aviez utilisé ===vous auriez dû explicitement penser à cela et vous n'auriez pas commis l'erreur.
Timmmm
44
"les règles sont impossibles à retenir" <== c'est la seule chose qui me dissuade de faire quelque chose de "significatif" en Javascript. (et les calculs flottants qui posent des problèmes lors des exercices de calcul de base)
WernerCD
9
L' 3 - "5"exemple soulève un bon point en soi: même si vous utilisez exclusivement à des fins ===de comparaison, c'est simplement ainsi que les variables fonctionnent en Javascript. Il n'y a aucun moyen d'y échapper complètement.
Jarett Millard
23
@ Timmm note que je joue ici l'avocat du diable. Je n'utilise pas ==dans mon propre code, je trouve que c'est un anti-motif et je suis tout à fait d'accord pour dire que l'algorithme d'égalité abstraite est difficile à retenir. Ce que je fais ici, c'est l'argument avancé par les gens.
Benjamin Gruenbaum
95

JQuery utilise la construction

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

largement, comme raccourci pour le code équivalent:

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

C’est une conséquence de la spécification de langage ECMAScript, § 11.9.3, Algorithme de comparaison d’égalité abstraite , qui indique notamment 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.

et

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

Cette technique particulière est assez courante pour que JSHint ait un drapeau spécialement conçu à cet effet.

Robert Harvey
la source
10
Pas juste de répondre à votre propre question, je voulais répondre à cela :) == null or undefinedest le seul endroit où je n'utilise pas ===ou!==
pllee
26
Pour être juste, jQuery n’est guère un modèle de base de code. Après avoir lu la source jQuery plusieurs fois, c'est l'une de mes bases de code les moins préférées avec beaucoup de ternaires imbriquées, de bits mal définis, d'imbrication et de choses que j'aurais autrement évitées dans du code réel. Ne me prenez pas au mot - lisez-le github.com/jquery/jquery/tree/master/src puis comparez-le avec Zepto, qui est un clone de jQuery: github.com/madrobby/zepto/tree/master/src
Benjamin Gruenbaum
4
Notez également que Zepto semble ==utiliser par défaut et ne l’utilise que ===dans les cas où cela est nécessaire: github.com/madrobby/zepto/blob/master/src/event.js
Hé le
2
@Hey pour être juste Zepto n'est pas non plus un modèle de code - c'est tristement célèbre pour son utilisation __proto__et son forçage presque à lui seul dans la spécification du langage pour éviter de casser des sites Web mobiles.
Benjamin Gruenbaum
2
@BenjaminGruenbaum qui n'était pas un jugement fait appel à la qualité de leur base de code, soulignant simplement que différents projets suivent des conventions différentes.
Hey
15

Vérifier les valeurs pour nullou undefinedest une chose, comme cela a été expliqué abondamment.

Il y a autre chose, où ==brille:

Vous pouvez définir une comparaison de la >=manière suivante (les gens partent généralement de >mais je trouve cela plus élégant):

  • a > b <=> a >= b && !(b >= a)
  • a == b <=> a >= b && b >= a
  • a < bet a <= bsont laissés comme un exercice au lecteur.

Comme nous le savons, en JavaScript "3" >= 3et à "3" <= 3partir duquel vous obtenez 3 == "3". Vous pouvez faire remarquer que c’est une idée horrible de permettre la comparaison des chaînes et des nombres en analysant la chaîne. Mais étant donné que c'est ainsi que cela fonctionne, ==c'est absolument la bonne façon de mettre en œuvre cet opérateur de relation.

Le point positif ==est donc que cela correspond à toutes les autres relations. En d'autres termes, si vous écrivez ceci:

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

Vous utilisez ==déjà implicitement .

Passons maintenant à la question assez connexe de: Était-ce un mauvais choix d’implémenter la comparaison des nombres et des chaînes de la façon dont elle est mise en œuvre? Vu isolément, cela semble une chose plutôt stupide à faire. Mais dans le contexte d’autres parties de JavaScript et des DOM, c’est relativement pragmatique, étant donné que:

  • les attributs sont toujours des chaînes
  • les clés sont toujours des chaînes (le cas d'utilisation étant que vous utilisez une Objectcarte clairsemée des valeurs aux valeurs)
  • les valeurs entrées par l'utilisateur et de contrôle de formulaire sont toujours des chaînes (même si la source correspond input[type=number])

Pour de nombreuses raisons, il était logique de faire en sorte que les chaînes se comportent comme des nombres lorsque cela est nécessaire. Et en supposant que la comparaison de chaînes et la concaténation de chaînes aient des opérateurs différents (par exemple, ::pour la concation et une méthode de comparaison (où vous pouvez utiliser toutes sortes de paramètres concernant la sensibilité à la casse ou non)), cela serait en réalité moins compliqué. Mais cette surcharge d’opérateur est probablement en fait d’où vient le "Java" dans "JavaScript";)

back2dos
la source
4
Être juste >=n'est pas vraiment transitif. Il est tout à fait possible dans JS que ni a > bni a < bni b == a(par exemple:) NaN.
Benjamin Gruenbaum
8
@BenjaminGruenbaum: C'est comme dire que ce +n'est pas vraiment commutatif, parce que NaN + 5 == NaN + 5ça ne tient pas. Le fait est que >=fonctionne avec des valeurs numériques pour lesquelles ==fonctionne de manière cohérente. Il ne devrait pas être surprenant que "pas un nombre" soit par sa nature même pas un nombre-ish;)
back2dos
4
Donc, le comportement médiocre de ==est compatible avec le comportement médiocre de >=? Super, maintenant j'aurais aimé qu'il y ait un >==...
Eldritch Conundrum
2
@EldritchConundrum: Comme j'ai essayé de l'expliquer, le comportement de >=est plutôt cohérent avec le reste des API langage / standard. Dans sa totalité, JavaScript réussit à être plus que la somme de ses parties bizarres. Si vous voulez un >==, voudriez-vous aussi un strict +? En pratique, beaucoup de ces décisions facilitent beaucoup de choses. Donc, je ne me précipiterais pas pour les juger pauvres.
back2dos
2
@EldritchConundrum: Encore une fois: les opérateurs de relation sont destinés à comparer des valeurs numériques, un opérande pouvant en fait être une chaîne. Pour les types d'opérande pour lesquels >=est significatif, l' ==est tout autant - c'est tout. Personne ne dit que vous devriez comparer [[]]avec false. Dans des langages comme C, le résultat de ce niveau de non-sens est un comportement indéfini. Traite-le simplement de la même façon: ne le fais pas. Et tout ira bien. De plus, vous n'aurez pas besoin de vous souvenir de règles magiques. Et puis c'est en fait plutôt simple.
back2dos
8

En tant que mathématicien professionnel, je vois dans l'opérateur de similarité de Javscript == (également appelé "comparaison abstraite", "égalité lâche" ) une tentative de construction d'une relation d'équivalence entre entités, ce qui inclut les fonctions réflexive , symétrique et transitive . Malheureusement, deux de ces trois propriétés fondamentales échouent:

==n'est pas réflexif :

A == A peut être faux, par exemple

NaN == NaN // false

==n'est pas transitif :

A == Bet B == Censemble n'impliquent pas A == C, par exemple

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

Seule la propriété symétrique survit:

A == Bimplique B == A, quelle violation est probablement impensable dans tous les cas et conduirait à une rébellion sérieuse;)

Pourquoi les relations d'équivalence sont-elles importantes?

Parce que c’est le type de relation le plus important et le plus répandu, étayé par de nombreux exemples et applications. L'application la plus importante est la décomposition d'entités en classes d'équivalence , ce qui est en soi un moyen très pratique et intuitif de comprendre les relations. Et le fait de ne pas être l’équivalence entraîne l’absence de classes d’équivalence, ce qui entraîne à son tour le manque d’intuitivité et de complexité inutile qui est bien connu.

Pourquoi est-ce une si terrible idée d'écrire ==pour une relation de non-équivalence?

Parce que cela rompt notre familiarité et notre intuition, car toute relation intéressante de similarité, d’égalité, de congruence, d’isomorphisme, d’identité, etc. est une équivalence.

Conversion de type

Au lieu de s’appuyer sur une équivalence intuitive, JavaScript introduit la conversion de type:

L'opérateur d'égalité convertit les opérandes s'ils ne sont pas du même type, puis applique une comparaison stricte.

Mais comment définit-on la conversion de type? Via un ensemble de règles compliquées à de nombreuses exceptions?

Tentative de construction d'une relation d'équivalence

Booléens Clairement trueet falsene sont pas identiques et devraient être dans des classes différentes.

Nombres. Heureusement, l'égalité des nombres est déjà bien définie, dans laquelle deux nombres différents ne sont jamais dans la même classe d'équivalence. En mathématiques, c'est. En JavaScript, la notion de nombre est quelque peu déformée via la présence du plus exotique -0, Infinityet -Infinity. Notre intuition mathématique dicte que 0et -0devrait être dans la même classe (en fait l' -0 === 0est true), alors que chaque infini est une classe séparée.

Nombres et Booléens. Étant donné les classes de nombres, où plaçons-nous les booléens? falsedevient semblable à 0, alors que truedevient similaire 1mais pas un autre nombre:

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

Y at-il une logique ici pour mettre en trueplace 1? Certes, 1est distingué, mais tel est le cas -1. Je ne vois personnellement aucune raison de convertir trueà 1.

Et ça devient encore pire:

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

Ainsi trueest en effet converti dans 1tous les nombres! Est-ce logique? Est-ce intuitif? La réponse est laissée comme exercice;)

Mais qu'en est-il de ceci:

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

Le seul booléen xà l' x && trueêtre trueest x = true. Ce qui prouve que les deux 1et 2(et tout autre nombre que 0) se convertissent en true! Cela montre que notre conversion échoue avec une autre propriété importante - la bijection . Cela signifie que deux entités différentes peuvent se convertir en une seule. Ce qui, en soi, ne doit pas être un gros problème. Le gros problème se pose lorsque nous utilisons cette conversion pour décrire une relation de "similitude" ou "d'égalité lâche" de ce que nous voulons l'appeler. Mais une chose est claire: il ne s'agira pas d'une relation d'équivalence ni d'une description intuitive via des classes d'équivalence.

Mais pouvons-nous faire mieux?

Au moins mathématiquement - certainement oui! Une simple relation d'équivalence entre les booléens et les nombres pourrait être construite avec falseet appartenant 0à la même classe. Ainsi false == 0serait la seule égalité lâche non-triviale.

Qu'en est-il des chaînes?

Nous pouvons couper les chaînes des espaces au début et à la fin pour les convertir en nombres, nous pouvons également ignorer les zéros au début:

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

Nous obtenons donc une règle simple pour une chaîne - découpez les espaces et les zéros devant. Soit nous obtenons un nombre ou une chaîne vide, auquel cas nous convertissons ce nombre ou ce chiffre à zéro. Ou nous n'obtenons pas de nombre, auquel cas nous ne convertissons pas et n'obtenons donc aucune nouvelle relation.

De cette façon, nous pourrions obtenir une relation d’équivalence parfaite sur l’ensemble des booléens, des nombres et des chaînes! Sauf que ... Les designers JavaScript ont évidemment un autre avis:

' ' == '' // false

Ainsi, les deux chaînes vers lesquelles les deux se convertissent 0sont soudainement non similaires! Pourquoi ou pourquoi Selon la règle, les chaînes sont vaguement égales précisément lorsqu'elles sont strictement égales! Non seulement cette règle casse la transitivité comme on le voit, mais elle est également redondante! Quel est l'intérêt de créer un autre opérateur ==pour le rendre strictement identique à l'autre ===?

Conclusion

L’opérateur d’égalité lâche ==aurait pu être très utile s’il respectait certaines lois mathématiques fondamentales. Mais comme ce n'est malheureusement pas le cas, son utilité en souffre.

Dmitri Zaitsev
la source
Qu'en est- il NaN? De plus, à moins qu'un format de nombre spécifique ne soit imposé pour la comparaison avec des chaînes, il faut obtenir une comparaison de chaîne non intuitive ou une non-transitivité.
Salomon Ucko
@SolomonUcko NaNagit comme un mauvais citoyen :-). La transivité peut et doit être confirmée pour toute comparaison d’équivalence, intuitive ou non.
Dmitri Zaitsev
7

Oui, j'ai rencontré un cas d'utilisation, en particulier lorsque vous comparez une clé avec une valeur numérique:

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

Je pense qu'il est beaucoup plus naturel de faire la comparaison comme key == some_numberplutôt que comme Number(key) === some_numberou comme key === String(some_number).

Mehrdad
la source
3

J'ai rencontré une application très utile aujourd'hui. Si vous souhaitez comparer des nombres complétés, comme 01avec des entiers normaux, ==fonctionne parfaitement. Par exemple:

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

Cela vous évite de supprimer le 0 et de convertir en entier.

Jon Snow
la source
4
Je suis à peu près sûr que la "bonne" façon de faire est '04'-0 === 4, ou peutparseInt('04', 10) === 4
ratbum le
Je n'étais pas au courant que tu pouvais faire ça.
Jon Snow
7
On me le dit souvent.
Jon Snow
1
@ratbum ou+'01' === 1
Eric Lagergren le
1
'011' == 011 // falseen mode non strict et SyntaxError en mode strict. :)
Brian S
3

Je sais que c'est une réponse tardive, mais il semble y avoir une confusion possible entre nullet undefined, qui à mon humble avis est ce qui fait le ==mal, plus encore que le manque de transitivité, ce qui est déjà assez grave. Considérer:

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

Qu'est-ce que cela signifie?

  • p1 a un superviseur dont le nom est "Alice".
  • p2 a un superviseur dont le nom est "Aucun".
  • p3explicitement, sans équivoque, n'a pas de superviseur .
  • p4peut ou peut avoir un superviseur. Nous ne savons pas, nous ne nous en soucions pas, nous ne sommes pas censés savoir (problème de confidentialité?), Ce n'est pas notre affaire.

Lorsque vous utilisez, ==vous confondez nullet undefinedce qui est totalement inapproprié. Les deux termes signifient des choses complètement différentes! Dire que je n'ai pas de superviseur simplement parce que j'ai refusé de vous dire qui est mon superviseur est faux!

Je crois comprendre que certains programmeurs ne se soucient pas de cette différence entre nullet undefinedou choisissent d’utiliser ces termes différemment. Et si votre monde ne pas utiliser nullet undefinedcorrectement, ou si vous souhaitez donner à votre propre interprétation de ces termes, ainsi que ce soit. Je ne pense pas que ce soit une bonne idée cependant.

En passant, je n'ai aucun problème avec nullet les undefineddeux sont faussaires! C'est parfaitement correct de dire

if (p.supervisor) { ... }

et ensuite null, undefinedle code qui traite le superviseur sera ignoré. C'est exact, car nous ne savons pas ou n'avons pas de superviseur. Tout bon. Mais les deux situations ne sont pas égales . C'est pourquoi ==est faux. Encore une fois, les choses peuvent être faussées et utilisées dans le sens de la frappe de canard, ce qui est excellent pour les langages dynamiques. C'est JavaScript, Pythonic, Rubyish, etc., mais ces choses ne sont PAS égales.

Et ne me lancez pas sur la non-transitivité: "0x16" == 10, 10 == "10"mais pas "10" == "0x16". Oui, JavaScript est faiblement typé. Oui, c'est coercitif. Mais la coercition ne devrait jamais, jamais, s'appliquer à l'égalité.

À propos, Crockford a des opinions bien arrêtées. Mais tu sais quoi? Il a raison ici!

FWIW Je comprends qu’il existe et que j’ai personnellement été confronté à des situations ==pratiques. C'est comme prendre une chaîne de caractères pour les nombres et, par exemple, comparer à 0. Cependant, c'est du piratage. Vous avez la commodité comme compromis pour un modèle imprécis du monde.

TL; DR: la fausseté est un très bon concept. Cela ne devrait pas aller jusqu'à l'égalité.

Ray Toal
la source
Merci de nous avoir montré les différentes situations :) Cependant, vous manquez p5... La seule situation dans typeof(p5.supervisor) === typeof(undefined)laquelle le superviseur n’existe même pas en tant que concept: D
TheCatWhisperer