web-dev-qa-db-fra.com

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

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

JavaScript a deux ensembles d'opérateurs d'égalité: === et !==, et leurs mauvais jumeaux == et !=. Les bons fonctionnent comme vous vous en doutez. Si les deux opérandes sont du même type et ont la même valeur, alors === produit true et !== produit false. Les mauvais jumeaux font la bonne chose lorsque 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 immémoriales. Voici quelques-uns des 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. À la place, utilisez toujours === et !==. Toutes les comparaisons que nous venons de montrer produisent false avec le === opérateur.

Compte tenu de cette observation sans équivoque, existe-t-il un moment où utiliser == pourrait être approprié?

280
Robert Harvey

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 seulement l'opinion . Il y a d'autres comme le créateur de langage Brendan Eich qui ne voient pas le gros problème avec ==. L'argument ressemble un peu à ce qui suit:

JavaScript est un langage typé comportemental *. 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 méthode .map D'un tableau sur une NodeList ou sur un jeu de sélection jQuery. C'est aussi pourquoi vous pouvez faire 3 - "5" Et obtenir quelque chose de significatif - 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:

  • Lecture d'un nombre de l'utilisateur - lire le .value D'un élément d'entrée dans le DOM? Aucun problème! Vous n'avez pas besoin de commencer à le diffuser ou à vous soucier de son type - vous pouvez le == Tout de suite en chiffres et obtenir quelque chose de significatif.
  • Besoin de vérifier l '"existence" d'une variable déclarée? - vous pouvez == null Car le comportement null représente qu'il n'y a rien là et indéfini n'a rien là non plus.
  • Besoin de vérifier si vous avez reçu une entrée 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 blanc pour vous qui est probablement ce dont vous avez besoin.

Regardons les exemples de Crockford et expliquons-les sur le plan du comportement:

'' == '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, == 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 certainement utile de le faire - surtout si vous prenez ce paradigme de traitement des types basés sur le comportement à l'échelle du langage.

* certains pourraient préférer le typage structurel du nom qui est plus courant mais il y a une différence - pas vraiment intéressé à discuter de la différence ici.

233
Benjamin Gruenbaum

Il s'avère que jQuery utilise la construction

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

largement, comme raccourci pour le code équivalent:

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

Ceci est une conséquence de la spécification du langage ECMAScript § 11.9.3, The Abstract Equality Comparison Algorithm , qui stipule, entre autres, 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 suffisamment courante pour que JSHint possède un flag spécialement conçu pour cela.

95
Robert Harvey

Vérifier les valeurs de null ou undefined est une chose, comme cela a été abondamment expliqué.

Il y a une autre chose, où == Brille:

Vous pouvez définir la comparaison à partir de >= Ainsi (les gens commencent généralement à partir de > Mais je trouve cela plus élégant):

  • a > b <=> a >= b && !(b >= a)
  • a == b <=> a >= b && b >= a
  • a < b Et a <= b Sont laissés au lecteur comme exercice.

Comme nous le savons, en JavaScript "3" >= 3 Et "3" <= 3, D'où vous obtenez 3 == "3". Vous pouvez faire remarquer que c'est une horrible idée de permettre l'implémentation de la comparaison entre les chaînes et les nombres en analysant la chaîne. Mais étant donné que c'est ainsi que cela fonctionne, == Est absolument la façon correcte d'implémenter cet opérateur de relation.

Donc, la très bonne chose à propos de == Est qu'elle est cohérente avec toutes les autres relations. Pour le dire différemment, 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 manière dont elle est implémentée? Vu isolément, cela semble une chose plutôt stupide à faire. Mais dans le contexte d'autres parties de JavaScript et du 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 un Object pour avoir une carte clairsemée des entiers aux valeurs)
  • les valeurs de saisie utilisateur et de contrôle de formulaire sont toujours des chaînes (même si la source correspond à input[type=number])

Pour un certain nombre de raisons, il était logique de faire en sorte que les chaînes se comportent comme des nombres en cas de besoin. 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 concaténation et une méthode de comparaison (où vous pouvez utiliser toutes sortes de paramètres concernant la sensibilité à la casse et ce qui ne l'est pas)), cela serait en fait moins d'un gâchis. Mais cette surcharge d'opérateur est probablement en fait d'où vient le "Java" dans "JavaScript";)

15
back2dos

En tant que mathématicien professionnel, je vois dans opérateur de similitude de Javscript== (aussi appelé "comparaison abstraite", "égalité lâche" ) une tentative de construire une relation d'équivalence entre les entités, ce qui inclut le fait d'être réflexif , symétrique et transitif . 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 == B et B == C ensemble n'implique pas A == C, par exemple.

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

Seule la propriété symétrique survit:

A == B implique B == A, laquelle violation est probablement impensable en tout cas et conduirait à une rébellion grave;)

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

Parce que c'est le type de relation le plus important et le plus répandu, soutenu par de nombreux exemples et applications. L'application la plus importante est la décomposition des entités en classes d'équivalence , qui est elle-même une manière très pratique et intuitive de comprendre les relations. Et l'échec de l'équivalence conduit au manque de classes d'équivalence, qui à son tour conduit au 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 qu'elle rompt notre familiarité et notre intuition, car littéralement toute relation intéressante de similitude, 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 la conversion de type est-elle définie? Via un ensemble de règles compliquées avec de nombreuses exceptions?

Tenter de construire une relation d'équivalence

Booléens. Clairement true et false ne sont pas identiques et doivent ê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, Infinity et -Infinity. Notre intuition mathématique nous dit que 0 et -0 devrait être dans la même classe (en fait -0 === 0 est true), tandis que chacun des infinis est une classe distincte.

Nombres et booléens. Compte tenu des classes de nombres, où mettons-nous les booléens? false devient similaire à 0, tandis que true devient similaire à 1 mais pas d'autre numéro:

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

Y a-t-il une logique ici pour mettre true avec 1? Certes 1 se distingue, mais -1. Personnellement, je ne vois aucune raison de convertir true en 1.

Et ça devient encore pire:

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

Donc true est en effet converti en 1 parmi tous les chiffres! Est-ce logique? Est-ce intuitif? La réponse est laissée comme exercice;)

Mais qu'en est-il:

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

Le seul booléen x avec x && true étant true est x = true. Ce qui prouve que les deux 1 et 2 (et tout autre numéro que 0) convertir en true! Ce que cela montre, c'est que notre conversion échoue à une autre propriété importante - être 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 "égalité lâche" de tout ce que nous voulons l'appeler. Mais une chose est claire - ce ne sera pas une relation d'équivalence et cela ne sera pas décrit intuitivement via des classes d'équivalence.

Mais pouvons-nous faire mieux?

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

Et les cordes?

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

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

Nous obtenons donc une règle simple pour une chaîne - couper les espaces et les zéros devant. Soit nous obtenons un nombre ou une chaîne vide, auquel cas nous convertissons en ce nombre ou 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 total des booléens, des nombres et des chaînes! Sauf que ... les concepteurs JavaScript ont évidemment une autre opinion:

' ' == '' // false

Ainsi, les deux chaînes converties en 0 sont soudainement non similaires! Pourquoi ou pourquoi? Selon la règle, les chaînes sont vaguement égales précisément quand elles sont strictement égales! Non seulement cette règle rompt 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 avait respecté certaines lois mathématiques fondamentales. Mais comme il ne le fait malheureusement pas, son utilité en souffre.

8
Dmitri Zaitsev

Oui, j'ai rencontré un cas d'utilisation pour cela, à savoir lorsque vous comparez un 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 d'effectuer la comparaison en tant que key == some_number Plutôt que Number(key) === some_number ou key === String(some_number).

7
user541686

J'ai rencontré une application assez utile aujourd'hui. Si vous souhaitez comparer des nombres remplis, comme 01 en entiers normaux, == fonctionne très bien. Par exemple:

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

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

3
Jon Snow

Je sais que c'est une réponse tardive, mais il semble y avoir une confusion possible à propos de null et undefined, ce qui à mon humble avis est ce qui fait == 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".
  • p3 explicitement, sans équivoque, n'a pas de superviseur .
  • p4 peut ou peut avoir un superviseur. Nous ne savons pas, nous ne nous soucions pas, nous ne sommes pas censés savoir (problème de confidentialité?), Car ce n'est pas notre affaire.

Lorsque vous utilisez == vous confondez null et undefined ce qui est tout à fait 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 comprends qu'il y a des programmeurs qui ne se soucient pas de cette différence entre null et undefined ou choisissent d'utiliser ces termes différemment. Et si votre monde n'utilise pas correctement null et undefined, ou si vous souhaitez donner votre propre interprétation de ces termes, qu'il en soit ainsi. Je ne pense pas que ce soit une bonne idée.

Maintenant, d'ailleurs, je n'ai aucun problème avec null et undefined étant tous deux fausses! Il est parfaitement normal de dire

if (p.supervisor) { ... }

puis null et undefined entraîneraient l'omission du code qui traite le superviseur. C'est exact, car nous ne connaissons 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 fausses et utilisées dans un sens de frappe de canard, ce qui est idéal pour les langages dynamiques. Il s'agit de JavaScript, Pythonic, Rubyish, etc., mais encore une fois, ces choses ne sont PAS égales.

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

Soit dit en passant, Crockford a des opinions bien arrêtées. Mais tu sais quoi? Il a raison ici!

FWIW Je comprends qu'il y a, et j'ai personnellement rencontré des situations où == est pratique! Comme prendre une chaîne pour les nombres et, par exemple, comparer à 0. Cependant, c'est du hack. Vous avez la commodité d'un compromis pour un modèle inexact du monde.

TL; DR: la fausseté est un excellent concept. Elle ne doit pas s'étendre à l'égalité.

3
Ray Toal