Skip to content

Latest commit

 

History

History
1718 lines (1191 loc) · 58.3 KB

README-fr-fr.md

File metadata and controls

1718 lines (1191 loc) · 58.3 KB

What the f*ck JavaScript?

WTFPL 2.0 NPM version

Une liste d'exemples JavaScript drôles et délicats

Le JavaScript est un langage formidable! Il possède une syntaxe simple, un grand écosystème et, le plus important de tout, une immense communauté.

En même temps, nous savons tous que le JavaScript est un langage assez amusant comprenant des aspects plus complexes que d'autres. Certains d'entre eux peuvent rapidement faire de notre travail quotidien un enfer, tout comme d'autres peuvent nous faire rire aux éclats.

L'idée originale de WTFJS appartient à Brian Leroux. Cette liste est fortement inspirée par son discours “WTFJS” at dotJS 2012:

dotJS 2012 - Brian Leroux - WTFJS

Le Manuscript sous forme de paquet Node

Vous pouvez installer ce manuel en utilisant npm. Pour cela, il suffit d'exécuter :

$ npm install -g wtfjs

Vous devriez pouvoir ensuite utiliser wtfjs en ligne de commande. Cela ouvrira le manuel dans votre terminal. Sinon, vous pouvez continuer à lire ici tout simplement.

La source du package est disponible ici: https://github.com/denysdovhan/wtfjs

Traductions

Actuellement, il existe des traductions de ** wtfjs ** pour les langues suivantes :

Demander une autre traduction

Table of Contents

💪🏻 Motivation

Juste pour le fun

“Just for Fun: The Story of an Accidental Revolutionary”, Linus Torvalds

L'objectif principal de cette liste est de rassembler quelques exemples loufoques et d'expliquer leur fonctionnement, quand c'est possible. 😉 Tout simplement parce qu'il est amusant d'apprendre quelque chose qu'on ne connaissait pas auparavant.

Si vous êtes débutant, vous pouvez aussi utiliser ces notes pour approfondir vos connaissances en JavaScript. J'espère qu'elles vous inciteront à passer plus de temps à lire la spécification.

Si vous êtes un développeur professionnel, vous pouvez considérer ces exemples comme une excellente référence pour toutes les bizarreries et comportements inattendus de notre langage bien-aimé, le JavaScript.

Dans tous les cas, lisez ce qui suit. Vous y trouverez probablement quelque chose de nouveau !

✍🏻 Notation

// -> est utilisé pour afficher le résultat d'une expression. Par exemple :

1 + 1; // -> 2

// > définit le résultat de console.log ou tout autre sortie. Par exemple :

console.log("hello, world!"); // > hello, world!

// indique un commentaire utilisé pour donner des explications. Par exemple :

// Assigner une fonction la constant foo
const foo = function() {};

👀 Exemples

[] est égal à ![]

Tableau est égal à pas tableau

[] == ![]; // -> true

💡 Explication :

L'opérateur de comparaison d'égalité faible convertit les deux côtés en nombres pour les comparer. Pour différentes raisons, les deux côtés deviennent le nombre 0.

Les tableaux sont truthy, donc à droite, on trouve l'opposé d'une valeur truthy, soit false, qui est ensuite forcé à 0. A gauche, puisqu'un tableau vide est forcé à 0 automatiquement, sans devoir être précédemment transformé en booléen, on trouve aussi 0, malgré le fait qu'un tableau soit truthy.

Voici comment cette expression se simplifie:

+[] == +![];
0 == +false;
0 == 0;
true;

Voir aussi [] est truthy, mais pas true.

true n'est pas égal à ![], mais pas égal à [] aussi

Un tableau n'est pas égal à true, tout comme pas tableau. Un tableau est égal à false, pas tableau est égal à false aussi :

true == []; // -> false
true == ![]; // -> false

false == []; // -> true
false == ![]; // -> true

💡 Explication :

true == []; // -> false
true == ![]; // -> false

// Selon la spécification

true == []; // -> false

toNumber(true); // -> 1
toNumber([]); // -> 0

1 == 0; // -> false

true == ![]; // -> false

![]; // -> false

true == false; // -> false
false == []; // -> true
false == ![]; // -> true

// Selon la spécification

false == []; // -> true

toNumber(false); // -> 0
toNumber([]); // -> 0

0 == 0; // -> true

false == ![]; // -> false

![]; // -> false

false == false; // -> true

true est faux

!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true

💡 Explication :

Considérez ceci étape par étape :

// `true` est 'truthy' et est représenté par la valeur 1 (nombre), 'true' sous forme de chaîne est NaN.
true == "true"; // -> false
false == "false"; // -> false

// 'false' n'est pas une chaîne vide, donc c'est une valeur `truthy`
!!"false"; // -> true
!!"true"; // -> true

baNaNa

"b" + "a" + +"a" + "a"; // -> 'baNaNa'

Ceci est une blague "old school" en JavaScript, mais remasterisée. Voici l'originale :

"foo" + +"bar"; // -> 'fooNaN'

💡 Explication :

L'expression est évaluée comme 'foo' + (+'bar'), ce qui convertit 'bar' à NaN.

NaN n'est pas un NaN

NaN === NaN; // -> false

💡 Explication :

La spécification définit strictement la logique derrière ce comportement :

  1. Si Type(x) est différent de Type(y), retourne false.
  2. Si Type(x) est Number, alors
    1. Si x est NaN, retourne false.
    2. Si y est NaN, retourne false.
    3. … … …

7.2.14 Strict Equality Comparison

Sur la base de la définition de NaN de l'IEEE :

Quatre relations mutuellement exclusives sont possibles : inférieur à, égal, supérieur à, et non ordonné. Le dernier cas survient quand au moins un opérande est NaN. Tous les NaN doivent se comparer de manière non ordonnée avec tout, y compris avec lui-même.

“What is the rationale for all comparisons returning false for IEEE754 NaN values?” sur StackOverflow.

C'est un échec

Vous ne le croiriez pas, mais …

(![] + [])[+[]] +
  (![] + [])[+!+[]] +
  ([![]] + [][[]])[+!+[] + [+[]]] +
  (![] + [])[!+[] + !+[]];
// -> 'fail'

💡 Explication :

En brisant cette masse de symboles en morceaux, nous remarquons que le schéma suivant se produit souvent :

![] + []; // -> 'false'
![]; // -> false

Donc, nous essayons d'ajouter [] à false, mais en raison d'un certain nombre d'appels de fonctions internes (binary + Operator -> ToPrimitive -> [[DefaultValue]]), nous finissons par convertir l'opérande de droite en chaîne :

![] + [].toString(); // 'false'

En considérant une chaîne comme un tableau, nous pouvons accéder à son premier caractère via [0] :

"false"[0]; // -> 'f'

Le reste est évident, sauf pour le i. Le i dans fail est saisi en générant la chaîne "falseundefined" et en saisissant l'éléments sur l'index [10].

[] est truthy, mais pas true

Un tableau est une valeur truthy, mais n'est pas égal à true.

!![]       // -> true
[] == true // -> false

💡 Explication :

Voici des liens vers les sections correspondantes de la spécification ECMA-262 :

null est falsy, mais pas faux

Malgré le fait que null soit une valeur falsy, elle n'est pas égale à false.

0 == false; // -> true
"" == false; // -> true

💡 Explication :

L'explication est la même que pour l'exemple précédent. Voici le lien correspondant :

document.all est un objet, mais il est undefined

⚠️ Ceci fait partie de la Browser API et ne fonctionnera pas dans un environnement Node.js ⚠️

Malgré le fait que document.all soit un objet de type tableau et qu'il donne accès aux nœuds DOM de la page, il répond à la fonction typeof comme étant undefined.

document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'

En même temps, document.all n'est pas égal à undefined.

document.all === undefined; // -> false
document.all === null; // -> false

Mais, parallèlement :

document.all == null; // -> true

💡 Explication :

document.all était anciennement un moyen d'accéder aux éléments DOM, principalement avec les anciennes versions d'IE. Bien que cela n'ait jamais été une norme, document.all était largement utilisé dans "l'ancien code JS". Quand la norme a progressé avec la venue de nouvelles API (par exemple, document.getElementById), l'API document.all est devenue obsolète et le comité de normes a dû décider ce qu'ils allaient en faire. En raison de sa large utilisation, ils ont décidé de la conserver, mais d'introduire une violation volontaire de la spécification JavaScript. La raison pour laquelle document.all retourne false lors de l'utilisation de l'opérateur d'égalité stricte (Strict Equality Comparison) avec undefined et true lors de l'utilisation de l'opérateur d'égalité abstraite (Abstract Equality Comparison) est due à la violation volontaire de la spécification qui le permet explicitement.

“Obsolete features - document.all” sur WhatWG - HTML spec. — “Chapter 4 - ToBoolean - Falsy values” sur YDKJS - Types & Grammar.

La valeur minimale est supérieure à zéro

Number.MIN_VALUE est le plus petit nombre, qui est supérieur à zéro :

Number.MIN_VALUE > 0; // -> true

💡 Explication :

Number.MIN_VALUE est 5e-324, c'est-à-dire le plus petit nombre positif pouvant être représenté dans la précision flottante et donc, c'est aussi le plus près que possible de zéro. Il définit la meilleure résolution que vous pouvez atteindre avec des valeurs flottantes.

Maintenant, la plus petite valeur globale est Number.NEGATIVE_INFINITY, bien que cette dernière ne soit pas vraiment numérique au sens strict.

“Why is 0 less than Number.MIN_VALUE in JavaScript?” at StackOverflow

Fonction n'est pas une fonction

⚠️ Une erreur présente dans la v5.5 (V8) ou inférieure de Node.js (Node.js <=7) ⚠️

Vous êtes tous au courant de l'irritant undefined is not a function, mais qu'en est-il de ceci :

// Déclare une classe qui _extends_ `null`
class Foo extends null {}
// -> [Function: Foo]

new Foo() instanceof null;
// > TypeError: function is not a function
// >     at … … …

💡 Explication :

Ceci ne fait pas partie de la spécification. C'est seulement une erreur qui a depuis été corrigé, il ne devrait donc plus y avoir de problème à l'avenir.

Ajout de tableaux

Et si vous essayiez d'additionner deux tableaux ?

[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'

💡 Explication :

C'est la concaténation ! Etape par étape, ça ressemble à ceci :

[1, 2, 3] +
  [4, 5, 6][
    // appelle toString()
    (1, 2, 3)
  ].toString() +
  [4, 5, 6].toString();
// concaténation
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");

Les virgules finales dans un tableau

Vous avez créé un tableau avec 4 éléments vides. Malgré tout, vous obtiendrez un tableau avec seulement trois éléments, à cause des virgules finales.

let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'

💡 Explication :

Les virgules finales (trailing commas en anglais) peuvent être utiles lors de l'ajout de nouveaux éléments, de paramètres ou de propriétés à du code JavaScript. Si vous voulez ajouter une nouvelle propriété, vous pouvez tout simplement ajouter une nouvelle ligne sans modifier la ligne précédente si cette ligne utilise déjà une virgule finale. Cela rend plus clair les différences dans un système de contrôle de version et l'édition de code pourrait être moins difficile.

Virgules finales (trailing commas) sur MDN.

L'égalité des tableaux est un monstre

En JavaScript, l'égalité des tableaux est un monstre, comme vous pouvez le voir ci-dessous :

[] == ''   // -> true
[] == 0    // -> true
[''] == '' // -> true
[0] == 0   // -> true
[0] == ''  // -> false
[''] == 0  // -> true

[null] == ''      // true
[null] == 0       // true
[undefined] == '' // true
[undefined] == 0  // true

[[]] == 0  // true
[[]] == '' // true

[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0  // true

[[[[[[ null ]]]]]] == 0  // true
[[[[[[ null ]]]]]] == '' // true

[[[[[[ undefined ]]]]]] == 0  // true
[[[[[[ undefined ]]]]]] == '' // true

💡 Explication :

Vous devriez regarder très attentivement les exemples ci-dessus ! Ce comportement est décrit dans la section 7.2.13 Abstract Equality Comparison de la spécification.

undefined et Number

Si nous ne transmettons aucun argument dans le constructeur Number, nous obtiendrons 0. La valeur undefined est attribuée aux arguments formels en l'absence d'arguments, alors vous pouvez vous attendre à ce que Number sans argument utilise undefined comme valeur de paramètre. Toutefois, quand nous lui passons undefined directement, nous obtiendrons NaN en retour.

Number(); // -> 0
Number(undefined); // -> NaN

💡 Explication :

Selon la spécification :

  1. Si aucun argument n'a été passé lors de l'appel de la fonction, n est +0.
  2. Sinon, n est ? ToNumber(value).
  3. Dans le cas d'undefined, ToNumber(undefined) doit retourner NaN.

Voici les sections correspondantes :

parseInt est un méchant

parseInt est célèbre pour ses bizarreries:

parseInt("f*ck"); // -> NaN
parseInt("f*ck", 16); // -> 15

💡 Explication : Ce résultat est dû au fait que parseInt continue d'analyser caractère par caractère la chaîne jusqu'à ce qu'il rencontre un caractère qu'il ne connaît pas. Le f dans 'f*ck' correspond au chiffre hexadécimal 15.

Analyser Infinity comme entier est quelque chose…

//
parseInt("Infinity", 10); // -> NaN
// ...
parseInt("Infinity", 18); // -> NaN...
parseInt("Infinity", 19); // -> 18
// ...
parseInt("Infinity", 23); // -> 18...
parseInt("Infinity", 24); // -> 151176378
// ...
parseInt("Infinity", 29); // -> 385849803
parseInt("Infinity", 30); // -> 13693557269
// ...
parseInt("Infinity", 34); // -> 28872273981
parseInt("Infinity", 35); // -> 1201203301724
parseInt("Infinity", 36); // -> 1461559270678...
parseInt("Infinity", 37); // -> NaN

Soyez prudent avec l'analyse de null aussi :

parseInt(null, 24); // -> 23

💡 Explication :

parsetInt convertit null sous forme de chaîne "null" et essait de la convertir. Pour les bases comprises entre 0 et 23, parseInt ne peut convertir aucun chiffre, donc parseInt renvoie NaN. Sur 24, "n", la 14ème lettre, est ajoutée au système de numération. Sur 31, "u", la 21ème lettre, est ajoutée et la chaîne entière peut être décodée. Sur 37, il n'y a plus de jeu de valeur numérique valide pouvant être générée, donc, NaN est renvoyé.

“parseInt(null, 24) === 23… wait, what?” sur StackOverflow.

N'oubliez pas les octaux:

parseInt("06"); // 6
parseInt("08"); // 8 si support ECMAScript 5
parseInt("08"); // 0 si pas support ECMAScript 5

💡 Explication : Si la chaîne d'entrée commence par 0, la base est égale à 8 (octal) ou à 10 (décimal). La base choisie dépend de l'implémentation. ECMAScript 5 indique que la valeur 10 (décimal) est utilisée, mais tous les navigateurs ne le prennent pas encore en charge. Pour cette raison, spécifiez toujours une base lorsque vous utilisez parseInt.

parseInt convertit toujours une entrée en chaîne :

parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2
Number({ toString: () => 2, valueOf: () => 1 }); // -> 1

Soyez prudent lors d'analyse de valeurs en virgule flottante :

parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5

💡 Explication : parseInt prend une chaîne de caractère comme argument et retourne un entier sur la base spécifiée. parseInt supprime aussi tout ce que suit, incluant le premier non-chiffre de la chaîne transmise en paramètre. 0.000001 est converti en chaîne "0.000001", et parseInt retourne 0. Quand 0.0000001 est converti en chaîne, il est traité comme "1e-7" et retourne donc 1. 1/1999999 est interprété comme étant 5.00000250000125e-7 et retourne 5.

Math avec true et false

Faisons des maths :

true +
  true(
    // -> 2
    true + true
  ) *
    (true + true) -
  true; // -> 3

Hmmm… 🤔

💡 Explication :

Avec le constructeur Number, nous pouvons forcer les valeurs aux nombres. Il est assez évident que true sera forcé à 1.

Number(true); // -> 1

L'opérateur unaire + tente de convertir sa valeur en nombre. Il peut convertir des représentations d'entiers et de nombres flottants sous forme de chaîne, de même que les valeurs true, false et null. S'il ne peut pas analyser une valeur particulière, il sera évalué à NaN. Cela signifie que nous pouvons contraindre true à 1 plus facilement :

+true; // -> 1

Lorsque vous effectuez une addition ou une multiplication, la méthode ToNumber est appelée. Selon la spécification, cette méthode retourne :

Si argument est true, retourne 1. Si argument est false, retourne +0.

C'est pourquoi nous pouvons ajouter des valeurs booléennes en tant que nombres réguliers et obtenir des résultats corrects.

Les sections correspondantes :

Les commentaires HTML sont valides en JavaScript

Vous serez impressionné, mais <!-- (connu sous le nom de commentaire HTML) est aussi valide comme commentaire en JavaScript.

// commentaire valide
<!-- commentaire valide aussi

💡 Explication :

Impressionné ? Les commentaires de type HTML étaient à la base destinés à permettre aux navigateurs ne comprenant pas la balise <script> de se dégrader avec élégance. Ces navigateurs, par exemple Netscape 1.x, ne sont plus populaires aujourd'hui. Il est donc inutile de mettre des commenaitres HTML dans vos balises <script>.

NaN n'est pas un nombre

typeof NaN est un 'number' :

typeof NaN; // -> 'number'

💡 Explication :

Explications sur le fonctionnement des opérateurs typeof et instanceof :

[] et null sont des objets

typeof []; // -> "object"
typeof null; // -> "object"

// however
null instanceof Object; // false

💡 Explication :

Le comportement de l'opérateur typeof est défini dans la section suivante de la spécification :

Selon la spécification, l'opérateur typeof renvoie une chaîne : Table 35: typeof Operator Results. Pour les objets null, ordinaires, standard exotic et non-standard exotic, qui n'implémentent pas [[Call]], il retourne la chaîne "object".

En revanche, vous pouvez vérifier le type d'un objet en utilisant la méthode toString.

Object.prototype.toString.call([]);
// -> '[object Array]'

Object.prototype.toString.call(new Date());
// -> '[object Date]'

Object.prototype.toString.call(null);
// -> '[object Null]'

Nombres magiquement croissant

999999999999999; // -> 999999999999999
9999999999999999; // -> 10000000000000000

10000000000000000; // -> 10000000000000000
10000000000000000 + 1; // -> 10000000000000000
10000000000000000 + 1.1; // -> 10000000000000002

💡 Explication :

Ceci est dû à la norme IEEE 754-2008 concernant l'arithmétique binaire en virgule flottante. À cette échelle, un nombre s'arrondit au nombre pair le plus près. Plus d'infos :

Précision de 0.1 + 0.2

Une blague bien connue. L'ajout de 0.1 et de 0.2 est mortellement précis :

0.1 +
  0.2(
    // -> 0.30000000000000004
    0.1 + 0.2
  ) ===
  0.3; // -> false

💡 Explication :

La réponse à la question ”Is floating point math broken?” sur StackOverflow:

Les constantes 0.2 et 0.3 seront également des approximations à leurs vraies valeurs. Il arrive que le double le plus proche de 0.2 soit supérieur au nombre rationnel 0.2, mais que le double le plus proche de 0.3 soit inférieur au nombre rationnel 0.3. La somme de 0.1 et 0.2 finit donc par être supérieure au nombre rationnel 0.3 et donc, en désaccord avec la constante de votre code.

Le problème est tellement connu qu'il y a même un site web appelé 0.30000000000000004.com. Il est récurrent pour tous les langages utilisant des mathématiques avec des virgules flottantes, pas seulement JavaScript.

Patching de numéros

Vous pouvez ajouter vos propres méthodes pour encapsuler des objets comme Number ou String.

Number.prototype.isOne = function() {
  return Number(this) === 1;
};

(1.0).isOne(); // -> true
(1).isOne(); // -> true
(2.0)
  .isOne()(
    // -> false
    7
  )
  .isOne(); // -> false

💡 Explication :

De toute évidence, vous pouvez extend l'objet Number comme n'importe quel autre objet en JavaScript, mais ce n'est toutefois pas recommandé si le comportement de la méthode définie ne fait partie de la spécification. Voici donc la liste des propriétés de Number :

Comparaison de trois nombres

1 < 2 < 3; // -> true
3 > 2 > 1; // -> false

💡 Explication :

Pourquoi est-ce que cela fonctionne ainsi ? Et bien le problème se trouve dans la première partie de l'expression. Voici comment cela fonctionne :

1 < 2 < 3; // 1 < 2 -> true
true < 3; // true -> 1
1 < 3; // -> true

3 > 2 > 1; // 3 > 2 -> true
true > 1; // true -> 1
1 > 1; // -> false

Nous pouvons résoudre ce problème avec l'opérateur Supérieur ou égal à (>=) :

3 > 2 >= 1; // true

En savoir plus sur les opérateurs relationnels dans la spécification :

Math drôle

Souvent, les résultats d'opérations arithmétiques en JavaScript peuvent être assez inattendus. Considérez ces exemples :

 3  - 1  // -> 2
 3  + 1  // -> 4
'3' - 1  // -> 2
'3' + 1  // -> '31'

'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'

'222' - -'111' // -> 333

[4] * [4]       // -> 16
[] * []         // -> 0
[4, 4] * [4, 4] // NaN

💡 Explication :

Que se passe-t-il dans les quatre premiers exemples ? Voici un petit tableau pour comprendre les additions en JavaScript :

Nombre  + Nombre  -> addition
Booléen + Nombre  -> addition
Booléen + Booléen -> addition
Nombre  + Chaîne -> concaténation
Chaîne  + Booléen -> concaténation
Chaîne  + Chaîne  -> concaténation

Qu'en est-il des autres exemples ? Les méthodes ToPrimitive et ToString sont implicitement appelées pour [] et {} avant une addition. En lire plus sur le processus d'évalution dans la spécification :

Addition de RegExps

Saviez-vous que vous pouviez ajouter des nombres comme dans l'exemple ci-dessous ?

// Remplacement de la méthode toString
RegExp.prototype.toString =
  function() {
    return this.source;
  } /
  7 /
  -/5/; // -> 2

💡 Explication :

Les chaînes ne sont pas des instances de String

"str"; // -> 'str'
typeof "str"; // -> 'string'
"str" instanceof String; // -> false

💡 Explication :

Le constructeur String retourne une chaîne :

typeof String("str"); // -> 'string'
String("str"); // -> 'str'
String("str") == "str"; // -> true

Essayons avec un new :

new String("str") == "str"; // -> true
typeof new String("str"); // -> "object"

Un objet ? Qu'est-ce que c'est ?

new String("str"); // -> [String: 'str']

Plus d'infos sur le constructeur String dans la spécification :

Appeler des fonctions avec des caractères accent grave

Déclarons une fonction qui enregistre tous les paramètres dans la console :

function f(...args) {
  return args;
}

Aucun doute, vous savez qu'il est possible d'appeler cette fonction comme ceci :

f(1, 2, 3); // -> [ 1, 2, 3 ]

Mais saviez-vous que vous pouvez appeler n'importe quelle fonction avec des caractères accent grave (backticks en anglais) ?

f`true is ${true}, false is ${false}, array is ${[1, 2, 3]}`;
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// ->   true,
// ->   false,
// ->   [ 1, 2, 3 ] ]

💡 Explication :

Bon, ce n'est pas du tout magique si vous êtes familier des littéraux de gabarits étiquetés. Dans l'exemple ci-dessus, la fonction f est une étiquette pour littéral de gabarit. Les étiquettes avant un littéral de gabarit vous permettent d'analyser les littéraux de gabarits avec une fonction. Le premier argument d'une fonction étiquetée contient un tableau avec comme valeurs des chaînes. Les arguments restants sont liés aux expressions. Exemple :

function template(strings, ...keys) {
  // fait quelque chose avec `strings` et `keys`…
}

Voici la magic behind la célébre bibliothèque appelée 💅 styled-components, qui est populaire dans la communauté React.

Lien vers la spécification :

Call call call

Trouvé par @cramforce

console.log.call.call.call.call.call.apply(a => a, [1, 2]);

💡 Explication :

Attention, ceci pourrait vous casser la tête ! Essayez de reproduire ce code dans votre tête : nous appliquons la méthode call en utilisant la méthode apply. Plus d'infos :

Une propriété constructor

const c = "constructor";
c[c][c]('console.log("WTF?")')(); // > WTF?

💡 Explication :

Considérez cet exemple étape par étape :

// Déclare une nouvelle constante qui est une chaîne : "constructor"
const c = "constructor";

// `c` est une chaîne
c; // -> 'constructor'

// Pour obtenir le constructeur de la chaîne
c[c]; // -> [Function: String]

// Pour obtenir le constructeur du constructeur
c[c][c]; // -> [Function: Function]

// Appelle la Fonction constructeur et passe
// le corps d'une nouvelle fonction comme argument
c[c][c]('console.log("WTF?")'); // -> [Function: anonymous]

// Et ensuite appelle cette fonction anonyme
// Le résultat est un retour de console avec la chaîne "WTF?"
c[c][c]('console.log("WTF?")')(); // > "WTF?"

Un Object.prototype.constructor renvoie une référence à la fonction constructeur Object qui a créé l'instance de l'objet. Dans le cas des chaînes, il s'agit de String, dans le cas des nombres, il s'agit de Number, et ainsi de suite.

Object en tant que clé de la propriété d'un objet

{ [{}]: {} } // -> { '[object Object]': {} }

💡 Explication :

Pourquoi est-ce que ça marche ? Ici, nous utilisons un Computed property name. Quand vous passez un objet entre ces crochets, l'objet est forcé de devenir une chaîne, alors nous obtenons la clé [objet Object] et la valeur {}.

Nous pouvons créer des enfers de crochets et de parenthèses comme dans l'exemple ci-dessous :

({ [{}]: { [{}]: {} } }[{}][{}]); // -> {}

// structure:
// {
//   '[object Object]': {
//     '[object Object]': {}
//   }
// }

En savoir plus sur les objets littéraux ici:

Accéder aux prototypes avec __proto__

Comme nous le savons, les primitives n’ont pas de prototypes. Cependant, si nous essayons d'obtenir une valeur de __proto__ pour les primitives, nous obtiendrions ceci :

(1).__proto__.__proto__.__proto__; // -> null

💡 Explication :

Cela se produit car lorsque quelque chose n'a pas de prototype, il sera encapsulé dans un objet à l'aide de la méthode ToObject. Donc, étape par étape :

(1)
  .__proto__(
    // -> [Number: 0]
    1
  )
  .__proto__.__proto__(
    // -> {}
    1
  ).__proto__.__proto__.__proto__; // -> null

Voici plus d'infos sur __proto__ :

`${{Object}}`

Quel est le résultat de l'expression ci-dessous ?

`${{ Object }}`;

La réponse est :

// -> '[object Object]'

💡 Explication :

Nous avons défini un objet avec une propriété Object en utilisant une propriété de notation raccourcie :

{
  Object: Object;
}

Ensuite, nous avons passé cet objet au litéral de gabarit, ce qui fait que la méthode toString appelle donc cet objet. Voilà pourquoi nous obtenons la chaîne '[object Object]'.

Déstructuration avec des valeurs par défaut

Considérez cet exemple :

let x,
  { x: y = 1 } = { x };
y;

L'exemple ci-dessus est une bonne question pour un entretien. Quelle est la valeur de y ? La réponse est :

// -> 1

💡 Explication :

let x,
  { x: y = 1 } = { x };
y;
//  ↑       ↑           ↑    ↑
//  1       3           2    4

Avec l'exemple ci-dessus :

  1. On déclare x sans valeur, donc sa valeur est undefined.
  2. Ensuite, nous intégrons la valeur de x dans la propriété de l'objet x.
  3. Ensuite, nous extrayons la valeur de x en utilisant la déstructuration pour l'assigner à y. Si la valeur n'est pas déinie, nous utiliserons 1 comme valeur par défaut.
  4. Retourne la valeur de y.

Points et propagation

Des exemples intéressants pourraient être composés avec la propagation de tableaux. Considérez cela :

[...[..."..."]].length; // -> 3

💡 Explication :

Pourquoi 3 ? Lorsque nous utilisons le spread operator, la méthode @@iterator est appelée et l'itérateur renvoyé est utilisé pour obtenir les valeurs à itérer. L'itérateur par défaut pour une chaîne étend une chaîne en caractères. Après sa propagation, ces caractères sont empaquetés dans un tableau. Ensuite, ce tableau est à nouveau propagé et empaqueté encore une fois dans un tableau.

Une chaîne '...' est composée de trois caractères ., donc, la longueur du tableau résultant est de 3.

Maintenant, étape par étape :

[...'...']             // -> [ '.', '.', '.' ]
[...[...'...']]        // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3

Évidemment, nous pouvons étendre et encapsuler les éléments d’un tableau autant de fois que nous le souhaitons :

[...'...']                 // -> [ '.', '.', '.' ]
[...[...'...']]            // -> [ '.', '.', '.' ]
[...[...[...'...']]]       // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]]  // -> [ '.', '.', '.' ]
// etc.

Étiquettes

Peu de programmeurs connaissent les étiquettes en JavaScript. Elles sont plutôt intéressantes :

foo: {
  console.log("first");
  break foo;
  console.log("second");
}

// -> first
// -> undefined

💡 Explication :

La déclaration étiquetée est utilisée avec les déclarations break ou continue. Vous pouvez utiliser une étiquette pour identifier une boucle, puis ensuite, utiliser la déclaration break ou continue pour indiquer si un programme doit interrompre la boucle ou poursuivre son exécution.

Dans l'exemple ci-dessus, nous identifions une étiquette foo. Ensuite, console.log('first'); est exécuté et l'exécution est interrompue.

En savoir plus sur les étiquettes en JavaScript :

Étiquettes imbriquées

a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5

💡 Explication :

Similaire aux exemples précédents, suivez ces liens pour plus d'infos :

Try...catch insidieux

Que retournera cette expression ? 2 ou 3 ?

(() => {
  try {
    return 2;
  } finally {
    return 3;
  }
})();

La réponse est 3. Surprenant ?

💡 Explication :

Est-ce un héritage multiple ?

Regardez l'exemple ci-dessous :

new class F extends (String, Array) {}(); // -> F []

Est-ce un héritage multiple ? Non.

💡 Explication :

L'élément intéressant est la valeur de la clause extends ((String, Array)). L'opérateur de groupement retourne toujours son dernier argument, donc (String, Array) est en réalité simplement Array. Cela signifie que nous venons de créer une classe qui extends Array.

Un générateur qui se yield lui-même

Considérez cet exemple de générateur qui se yield lui-même :

(function* f() {
  yield f;
})().next();
// -> { value: [GeneratorFunction: f], done: false }

Comme vous pouvez le constater, la valeur renvoyée est un objet dont la valeur est égale à f. Dans ce cas, nous pouvons faire quelque chose semblable à ça :

(function* f() {
  yield f;
})()
  .next()
  .value()
  .next()(
    // -> { value: [GeneratorFunction: f], done: false }

    // and again
    function* f() {
      yield f;
    }
  )()
  .next()
  .value()
  .next()
  .value()
  .next()(
    // -> { value: [GeneratorFunction: f], done: false }

    // and again
    function* f() {
      yield f;
    }
  )()
  .next()
  .value()
  .next()
  .value()
  .next()
  .value()
  .next();
// -> { value: [GeneratorFunction: f], done: false }

// and so on
// …

💡 Explication :

Pour comprendre pourquoi cela fonctionne ainsi, lisez ces sections de la spécification :

Une classe de classe

Considérez cette syntaxe obfusquée :

typeof new class {
  class() {}
}(); // -> "object"

Il semblerait que nous déclarions une classe à l'intérieur d'une classe. Cela devrait être une erreur, cependant, nous obtenons la chaîne "object".

💡 Explication :

Depuis ECMAScript 5, les mots clés sont autorisés en tant que noms de propriétés. Réfléchissez comment vous le feriez pour cet exemple d'objet simple :

const foo = {
  class: function() {}
};

Et les définitions de méthode abrégée standard d'ES6. Aussi, les classes peuvent être anonymes. Donc, si nous supprimons la partie : function, nous obtiendrons :

class {
  class() {}
}

Le résultat d'une classe par défaut est toujours un objet simple. Et son typeof devrait retourner "object".

Plus d'infos ici :

Objets incoercibles

Avec des symboles bien connus, il existe un moyen de se débarrasser de la coercition de type. Examinez l'exemple ci-dessous :

function nonCoercible(val) {
  if (val == null) {
    throw TypeError(
      "inCoercible ne doit pas être appelé avec null ou undefined"
    );
  }

  const res = Object(val);

  res[Symbol.toPrimitive] = () => {
    throw TypeError("Tente de transformer un objet incoercible");
  };

  return res;
}

Maintenant, nous pouvons l'utiliser de cette manière :

// objets
const foo = nonCoercible({ foo: "foo" });

foo * 10; // -> TypeError: Tente de transformer un objet incoercible
foo + "evil"; // -> TypeError: Tente de transformer un objet incoercible

// chaînes
const bar = nonCoercible("bar");

bar + "1"; // -> TypeError: Tente de transformer un objet incoercible
bar.toString() + 1; // -> bar1
bar === "bar"; // -> false
bar.toString() === "bar"; // -> true
bar == "bar"; // -> TypeError: Tente de transformer un objet incoercible

// nombres
const baz = nonCoercible(1);

baz == 1; // -> Tente de transformer un objet incoercible
baz === 1; // -> false
baz.valueOf() === 1; // -> true

💡 Explication :

Fonctions fléchées complexes

Considérez l'exemple ci-dessous :

let f = () => 10;
f(); // -> 10

D'accord, d'accord, mais qu'en est-il de celui-ci :

let f = () => {};
f(); // -> undefined

💡 Explication :

Vous pourriez vous attendre à obtenir {} au lieu d'undefined. C'est dû au fait que les accolades font partie de la syntaxe des fonctions fléchées; f renverra donc undefined. Il est cependant possible de renvoyer l'objet {} directement à partir d'une fonction fléchée en mettant la valeur de retour entre parenthèses :

let f = () => ({});
f(); // -> {}

Les fonctions fléchées ne peuvent pas être un constructeur

Considérez l'exemple ci-dessous :

let f = function() {
  this.a = 1;
};
new f(); // -> { 'a': 1 }

Maintenant, essayez de faire la même chose avec une fonction fléchée :

let f = () => {
  this.a = 1;
};
new f(); // -> TypeError: f is not a constructor

💡 Explication :

Les fonctions fléchées ne peuvent pas être utilisées en tant que constructeurs et produiront une erreur si elles sont utilisées avec new parce qu'elles ont un lexical this et pas de propriété prototype, cela n'aurait donc pas beaucoup de sens.

arguments et fonctions fléchées

Considérez l'exemple ci-dessous :

let f = function() {
  return arguments;
};
f("a"); // -> { '0': 'a' }

Maintenant, essayez de faire la même chose avec une fonction fléchée :

let f = () => arguments;
f("a"); // -> Uncaught ReferenceError: arguments is not defined

💡 Explication :

Les fonctions fléchées sont une version allégée des fonctions standards dans laquelle l'accent est mis sur la taille et le lexical this. En même temps, les fonctions fléchées ne fournissent pas de liaison pour l'objet arguments. Comme alternative valable, utilisez les paramètres rest pour obtenir le même résultat :

let f = (...args) => args;
f("a");

Retour difficile

La déclaration return est compliquée aussi. Considérez ceci :

(function() {
  return;
  {
    b: 10;
  }
})(); // -> undefined

💡 Explication :

return et l'expression renvoyée doivent être sur la même ligne :

(function() {
  return {
    b: 10
  };
})(); // -> { b: 10 }

Cela est dû au concept appelé "Insertion Automatique du Point-Virgule", qui insère automatiquement des points-virgules à la fin de la plupart des nouvelles lignes. Dans le premier exemple, un point-virgule est inséré entre return et l'objet littéral. La fonction renvoie donc undefined et l'objet littéral n'est jamais évalué.

Chaînage d'affectations sur un objet

var foo = { n: 1 };
var bar = foo;

foo.x = foo = { n: 2 };

foo.x; // -> undefined
foo; // -> {n: 2}
bar; // -> {n: 1, x: {n: 2}}

De droite à gauche, {n: 2} est affecté à foo, et le résultat de cette affectation {n: 2} est affecté à foo.x. C'est pourquoi bar retourne {n: 1, x: {n: 2}}, puisque bar est une référence à foo. Mais pourquoi foo.x retourne undefined, alors que ce n'est pas le cas pour bar.x ?

💡 Explication :

foo et bar font référence au même objet {n: 1}, et les lvalues sont résolues avant les assignations. foo = {n: 2} crée un nouvel objet, et donc foo est mis à jour pour référencer ce nouvel objet. L'astuce ici est foo dans foo.x = …, car une lvalue a été résolue au préalable et fait toujours référence à l'objet précédent foo = {n: 1} et donc le met à jour en ajoutant la valeur x.

Après cette chaîne d'assignation, bar, quant à lui, fait toujours référence à l'ancien objet foo, alors que foo fait référence au nouvel objet {n: 2}, où x n'existe pas.

C'est équivalent à :

var foo = { n: 1 };
var bar = foo;

foo = { n: 2 }; // -> {n: 2}
bar.x = foo; // -> {n: 1, x: {n: 2}}
// `bar.x` pointe sur l'adresse du nouvel objet `foo`
// ce n'est pas équivalent à : `bar.x = {n: 2}`

Accéder aux propriétés d'un objet avec des tableaux

var obj = { property: 1 };
var array = ["property"];

obj[array]; // -> 1

Qu'en est-il des tableaux pseudo-multidimensionnels ?

var map = {};
var x = 1;
var y = 2;
var z = 3;

map[[x, y, z]] = true;
map[[x + 10, y, z]] = true;

map["1,2,3"]; // -> true
map["11,2,3"]; // -> true

💡 Explication :

L'opérateur [] convertit l'expression passée en utilisant toString. Transformer un tableau composé d'un seul élément vers une chaîne est similaire à transformer l'élément du tableau en chaîne :

["property"].toString(); // -> 'property'

Opérateurs null et relationnels

null > 0; // false
null == 0; // false

null >= 0; // true

💡 Explication :

En bref, si null < 0 est false, alors null >= 0 est true. Lisez une explication détaillée ici.

Number.toFixed() affiche différents nombres

Number.toFixed() peut se comporter étrangement selon le navigateur utilisé. Voir cet exemple :

(0.7875).toFixed(3);
// Firefox: -> 0.787
// Chrome: -> 0.787
// IE11: -> 0.788
(0.7876).toFixed(3);
// Firefox: -> 0.788
// Chrome: -> 0.788
// IE11: -> 0.788

💡 Explication :

Alors que votre premier instinct peut être que IE11 a correct et que Firefox et Chrome ont faux, la réalité est que Firefox et Chrome obéissent plus directement aux normes relatives aux nombres (IEEE-754 Floating Point), alors qu'IE11 les désobéit minutieusement dans (ce qui est probablement) un effort de donner des résultats plus clairs.

Vous pouvez voir pourquoi cela se produit avec quelques tests rapides :

// Confirme le résultat curieux d'arrondir `5` vers le bas
(0.7875).toFixed(3); // -> 0.787
// Il semble que ce soit juste `5` lorsque vous développez
//  les limites de la précision de flottement de 64 bits (double précision)
(0.7875).toFixed(14); // -> 0.78750000000000
// Mais que se passe-t-il si vous allez au-delà de la limite ?
(0.7875).toFixed(20); // -> 0.78749999999999997780

Les nombres à virgule flottante ne sont pas stockés sous forme de liste de chiffres décimaux en interne, mais par le biais d'une méthodologie plus complexe qui produit de minuscules inexactitudes qui sont généralement arrondies par des appels à toString ou des appels similaires, mais qui sont réellement présentes en interne.

Dans ce cas, le 5 sur la fin était en réalité une fraction extrêmement petite, en dessous d'un vrai 5. Si vous arrondissez à une longueur raisonnable, vous obtenez un 5… mais ce n'est en réalité pas un 5 en interne.

Ceci étant dit, IE11 rapportera la valeur entrée uniquement avec des zéros ajoutés à la fin, même dans le cas de toFixed(20), car cela semble forcer la valeur arrondie pour réduire les problèmes liées aux limites matérielles.

Voir pour référence NOTE 2 sur la définition de ECMA-262 pour toFixed.

Math.max() est moins que Math.min()

Math.min(1, 4, 7, 2); // -> 1
Math.max(1, 4, 7, 2); // -> 7
Math.min(); // -> Infinity
Math.max(); // -> -Infinity
Math.min() > Math.max(); // -> true

💡 Explication :

Comparer null à 0

Les expressions suivantes semblent introduire une contradiction :

null == 0; // -> false
null > 0; // -> false
null >= 0; // -> true

Comment null peut-il être ni égal ni supérieur à 0, si null >= 0 est en fait true ? (Ceci fonctionne de la même manière avec inférieur ou égal à (<=).)

💡 Explication :

Les méthodes d'évaluation de ces trois expressions sont toutes différentes et sont responsables de la production de ce comportement inattendu.

Premièrement, la comparaison d'égalité abstraite null == 0. Normalement, si cet opérateur ne peut pas comparer correctement les valeurs d'un côté comme de l'autre, il convertit les deux en nombres et compare ensuite les nombres. Alors, vous pouvez vous attendre au comportement suivant :

// Ce n'est pas ce qui se passe
(null == 0 + null) == +0;
0 == 0;
true;

Cependant, suite à une lecture attentive de la spécification, la transformation du nombre n'a pas lieu sur un côté qui est null ou undefined. Par conséquent, si vous avez null sur un des deux côté du signe égal, l'autre côté doit être null ou undefined pour que l'expression retourne true. Comme ce n'est pas le cas, false est renvoyé.

Ensuite, la comparaison relationnelle null > 0. L'algorithme ici, contrairement à celui de l'opérateur d'égalité abstraite, va convertir null à un nombre. Donc, nous obtenons ce comportement :

null > 0
+null = +0
0 > 0
false

Finalement, la comparaison relationnelle null >= 0. Vous pourriez soutenir que cette expression devrait être le résultat de null > 0 || null == 0; si tel était le cas, les résultats ci-dessus signifieraient que ceci serait aussi false. Toutefois, l'opérateur >= fonctionne d'une manière très différente, qui est simplement d'utiliser le contraire de l'opérateur <. Parce que notre exemple ci-dessus, utilisant l'opérateur supérieur à, s'applique également à l'opérateur inférieur à, cela signifie que cette expression est réellement évaluée de la manière suivante :

null >= 0;
!(null < 0);
!(+null < +0);
!(0 < 0);
!false;
true;

Même redéclaration d'une variable

JavaScript autorise la redéclaration de variables :

a;
a;
// Ceci est aussi valide
a, a;

Et ça fonctionne aussi en mode strict :

var a, a, a;
var a;
var a;

💡 Explication :

Toutes les définitions fusionnent en une seule définition.

Comportement par défaut d'Array.prototype.sort()

Imaginez que vous ayez besoin de trier un tableau composé de nombres.

[ 10, 1, 3 ].sort() // -> [ 1, 10, 3 ]

💡 Explication :

L'ordre de tri par défaut est construit lors de la transformation des éléments en chaînes, puis en comparant leurs séquences de valeurs unitaires sur le jeu de caractères UTF-16.

Indice

Passez comparefn si vous essayez de trier n'importe quoi d'autre qu'une chaîne.

[ 10, 1, 3 ].sort((a, b) => a - b) // -> [ 1, 3, 10 ]

📚 Autres ressources

  • wtfjs.com — une collection d'irrégularités spéciales très particulières, d'incohérences et de moments terriblement non intuitifs pour le langage du Web.
  • Wat — Un discours éclair de Gary Bernhardt de CodeMash 2012.
  • What the... JavaScript? — Kyle Simpsons parle des tentatives de Forward 2 pour "sortir de l'absurdité" du JavaScript. Il veut vous aider à produire un code plus propre, plus élégant et plus lisible, puis inspirer les gens à contribuer à la communauté open-source.

🎓 Licence

CC 4.0

© Denys Dovhan