https://developer.mozilla.org/fr-FR/docs/Web/JavaScript/Equality_comparisons_and_sameness
ES6 propose quatre algorithmes distincts pour gérer les comparaisons d'égalité :
- Comparaison d'égalité abstraite (
==) - Comparaison d'égalité stricte (
===) : utilisée parArray.prototype.indexOf,Array.prototype.lastIndexOfet la correspondance de cas - Égalité de valeur nulle : employée par
%TypedArray%et le constructeurArrayBuffer, ainsi que par les opérationsMapetSet. Cette méthode sera intégrée àString.prototype.includesdans ES2016/ES7 - Égalité de valeur : utilisée pour tous les autres contextes
JavaScript met à disposition trois opérateurs différents pour comparer des valeurs :
- L'égalité stricte (triple égalité ou identité), utilisant
=== - L'égalité loose (double égalité), utilisant
== - La méthode
Object.is(nouveauté ECMAScript 2015/ES6)
Le choix de l'opérateur dépend du type de comparaison souhaité.
En résumé, lors de la comparaison de deux éléments, le double égal effectue une conversion de type. Le triple égal effectue la même comparaison sans conversion de type (si les types diffèrent, le résultat est toujours false). La méthode Object.is se comporte comme le triple égal, mais traite différemment les cas particuliers du NaN et des valeurs -0 et +0, de sorte que ces deux dernières ne sont pas considérées comme égales, tandis que Object.is(NaN, NaN) retourne true. (Généralement, utiliser le double égal ou le triple égal pour comparer NaN à NaN retourne false, conformément à la norme IEEE 754.) Il est important de noter que toutes ces différences concernent uniquement le traitement des primitives : aucun de ces trois opérateurs ne permet de comparer si deux variables sont structurellement analogues. Pour deux objets non-primitifs différents, même s'ils partagent la même structure, les trois opérateurs retourneront false.
Égalité stricte ===
L'opérateur de stricte égalité vérifie si deux valeurs sont égales sans effectuer de conversion implicite au préalable. Si les deux valeurs comparées sont de types différents, elles ne sont pas égales. Si elles sont du même type et de même valeur, et qu'aucune n'est de type number, alors elles sont égales. Enfin, si les deux valeurs sont de type number, elles sont considérées égales lorsqu'elles ne sont pas toutes deux NaN et qu'elles ont la même valeur numérique, ou lorsque les deux valeurs sont分别为 +0 et -0.
let entier = 0;
let texte = new String("0");
let chaine = "0";
let valeurBool = false;
console.log(entier === entier); // true
console.log(texte === texte); // true
console.log(chaine === chaine); // true
console.log(entier === texte); // false
console.log(entier === chaine); // false
console.log(texte === texte); // false
console.log(null === undefined); // false
console.log(texte === null); // false
console.log(texte === undefined); // false
Dans la pratique, l'opérateur de stricte égalité est presque toujours le bon choix. Pour les valeurs autres que numériques, cet opérateur utilise une sémantique claire : une valeur n'est égale qu'à elle-même. Pour les nombres, l'opérateur adopte une sémantique légèrement modifiée pour gérer deux cas particuliers. Le premier concerne les zéros flottants non signés : bien que la distinction entre +0 et -0 soit nécessaire pour résoudre certains problèmes mathématiques spécifiques, elle ne concerne pas la plupart des scénarios courants. L'opérateur considère ces deux valeurs comme égales. Le second cas implique les valeurs NaN, qui représentent les solutions à des problèmes mathématiques indéfinis comme l'addition de l'infini positif et de l'infini négatif. L'opérateur considère que NaN n'est égal à aucune autre valeur, y compris lui-même. (L'équation (x !== x) est vraie uniquement lorsque x est NaN)
Égalité loose ==
L'opérateur d'égalité compare deux valeurs en les convertissant d'abord au même type. Après la conversion (qui peut affecter un côté ou les deux côtés de l'équation), la comparaison finale suit les mêmes règles que l'opérateur de stricte égalité. L'opérateur d'égalité satisfait la propriété commutative.
L'opérateur d'égalité effectue les comparaisons selon le tableau suivant pour les différents types de valeurs :
| Valeur comparée B | |
|---|---|
| Valeur comparée A | Undefined |
| Null | true |
| Number | false |
| String | false |
| Boolean | false |
| Object | false |
Dans ce tableau, ToNumber(A) tente de convertir le paramètre A en nombre avant la comparaison, ce qui équivaut à utiliser l'opérateur unaire +. ToPrimitive(A) convertit le paramètre A en valeur primitive en essayant successivement les méthodes A.toString() et A.valueOf().
D'une manière générale, selon la spécification ECMAScript, tous les objets sont différents de undefined et de null. Cependant, la plupart des navigateurs permettent à une catégorie très restreinte d'objets (à savoir tous les objets document.all de la page) de jouer parfois le rôle d'undefined. C'est dans ce contexte que l'opérateur d'égalité fonctionne. Ainsi, la méthode IsFalsy(A) retourne true si et seulement si A imite undefined. Dans tous les autres cas, un objet n'est égal ni à undefined ni à null.
let entier = 0;
let texte = new String("0");
let chaine = "0";
let valeurBool = false;
console.log(entier == entier); // true
console.log(texte == texte); // true
console.log(chaine == chaine); // true
console.log(entier == texte); // true
console.log(entier == chaine); // true
console.log(texte == texte); // true
console.log(null == undefined); // true
// deux cas retournent false, sauf cas rares
console.log(texte == null);
console.log(texte == undefined);
Certains développeurs considèrent qu'il vaut mieux ne jamais utiliser l'opérateur d'égalité loose. Les résultats de l'opérateur de stricte égalité sont plus prévisibles, et comme aucune conversion implicite n'est effectuée, la comparaison est plus rapide.
Égalité de valeur
L'égalité de valeur répond au dernier cas d'utilisation : déterminer si deux valeurs sont fonctionnellement identiques dans toutes les circonstances. Ce cas d'utilisation illustre le principe de substitution de Liskov. Elle se manifeste lors des tentatives de modification de propriétés immuables :
// Ajout d'une propriété immuable au constructeur Number
Object.defineProperty(Number, "ZERO_NEGATIF",
{ value: -0, writable: false, configurable: false, enumerable: false });
function tenterMutation(v)
{
Object.defineProperty(Number, "ZERO_NEGATIF", { value: v });
}
Object.defineProperty génère une exception lors de la tentative de modification d'une propriété immuable si cette propriété est effectivement modifiée, sinon aucune exception n'est levée. Par exemple, si v est -0, aucune modification n'a lieu donc aucune exception n'est levée. Mais si v est +0, une exception est levée. Les propriétés immuables et les nouvelles valeurs définies utilisent la comparaison d'égalité par valeur.
L'égalité de valeur est fournie par la méthode Object.is.
Égalité de valeur nulle
Similaire à l'égalité de valeur, à la différence près qu'elle considère +0 et -0 comme égaux.
Spécification : égalité, égalité stricte et égalité de valeur
Dans ES5, l'égalité == est définie dans la Section 11.9.3, The Abstract Equality Algorithm ; l'égalité === dans 11.9.6, The Strict Equality Algorithm. (Veuillez consulter ces liens, ils sont concis et clairs. Conseil : commencez par lire l'algorithme d'égalité stricte). ES5 fournit également l'égalité de valeur, Section 9.12, The SameValue Algorithm, utilisée en interne par le moteur JavaScript. Hormis les différences dans le traitement des nombres en 11.9.6.4 et 9.12.4, cet algorithme est essentiellement identique à l'algorithme d'égalité stricte. ES6 expose simplement cet algorithme via Object.is.
On peut observer qu'avec les opérateurs double et triple égal, mis à part le contrôle de type en 11.9.6.1, l'algorithme d'égalité stricte est un sous-ensemble de l'algorithme d'égalité car 11.9.6.2–7 correspond à 11.9.3.1.a–f.
Comprendre le modèle des comparaisons d'égalité
Avant ES2015, on pouvait considérer que le double égal et le triple égal étaient dans une relation d'extension. Certains affirmaient que le double égal étend le triple égal car il fait tout ce que fait le triple égal, plus la conversion de type. Par exemple 6 == "6". D'autres pourraient dire que le triple égal étend le double égal car il exige également que les types des deux paramètres soient identiques, ajoutant ainsi davantage de restrictions. La compréhension dépend de la façon dont on aborde le problème.
Mais ce modèle de comparaison ne permet pas d'intégrer Object.is de ES2016. En effet, Object.is n'est ni plus loose que le double égal, ni plus strict que le triple égal, et ne se situe pas non plus entre les deux. Comme le montre le tableau suivant, cela est dû à la façon dont Object.is traite NaN. Si Object.is(NaN, NaN) était évalué comme false, on pourrait dire qu'il est plus strict que le triple égal car il peut distinguer -0 et +0. Mais le traitement de NaN montre que ce n'est pas le cas. Object.is doit être considéré comme ayant une utilisation particulière, sans être qualifié de plus loose ou plus strict que les autres opérateurs d'égalité.
Tableau comparatif
| 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 |
[1,2] |
"1,2" |
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 |
Quand utiliser Object.is ou le triple égal
De manière générale, à part pour le traitement de NaN, le seul aspect intéressant de Object.is concerne les métaprogrammations, notamment sa façon particulière de traiter le zéro, particulièrement importante pour les descripteurs de propriétés lorsque votre travail nécessite de refléter certaines caractéristiques de Object.defineProperty. Si votre travail ne nécessite pas ces fonctionnalités, vous devriez éviter Object.is et utiliser === à la place. Même si vous avez besoin de comparer deux NaN pour obtenir true, il est généralement plus simple d'écrire une fonction spéciale de vérification de NaN (en utilisant la méthode isNaN des anciennes versions d'ECMAScript) plutôt que de trouver une astuce pour que Object.is n'affecte pas la comparaison des zéros de signes différents.
Voici une liste non exhaustive des méthodes et opérateurs intégrés qui distinguent -0 et +0 :
- (opérateur unaire négatif)
De toute évidence, appliquer l'opérateur unaire négatif à 0 produit -0. Cependant, l'abstraction de cette expression peut provoquer la propagation de -0 sans que vous vous en rendiez compte. Considérons l'exemple suivant :
let forceArret = obj.masse * -obj.vitesse
Si obj.vitesse est 0 (ou s'évalue à 0), un -0 est créé à cet endroit et assigné comme valeur de forceArret.
Math.atan2``Math.ceil``Math.pow``Math.roundMême si aucun paramètre n'est -0, ces méthodes peuvent retourner -0. Par exemple, calculer une puissance avec Math.pow sur -Infinity avec un exposant impair négatif retourne toujours -0. Consultez la documentation respective de ces méthodes pour plus de détails.Math.floor``Math.max``Math.min``Math.sin``Math.sqrt``Math.tanCes méthodes peuvent également retourner -0 lorsque -0 est Passed en paramètre. Par exemple, Math.min(-0, +0) retourne -0. Consultez la documentation respective de ces méthodes pour plus de détails.~``<<``>>Ces opérateurs utilisent tous l'algorithme ToInt32 en interne. Comme le type entier interne sur 32 bits n'a qu'un seul 0 (sans distinction de signe), le signe de -0 n'est pas préservé après l'opération inverse. Par exemple, Object.is(~~(-0), -0) et Object.is(-0 << 2 >> 2, -0) retournent tous les deux false.Compter sur Object.is sans considérer le signe du zéro est dangereux. Bien sûr, si l'intention est de distinguer -0 et +0, Object.is fonctionne comme prévu.