Une liste d'exemples JavaScript amusants et délicats
JavaScript est un excellent langage. Il possède une syntaxe simple, un vaste écosystème et, ce qui est le plus important, une grande communauté.
En même temps, nous savons tous que JavaScript est un langage assez amusant avec des parties délicates. Certains d’entre eux peuvent rapidement transformer notre travail quotidien en enfer, et certains d’entre eux peuvent nous faire rire aux éclats.
L'idée originale de WTFJS appartient à Brian Leroux. Cette liste est fortement inspirée de son exposé « WTFJS » à dotJS 2012 :
Vous pouvez installer ce manuel en utilisant npm
. Exécutez simplement :
$ npm install -g wtfjs
Vous devriez maintenant pouvoir exécuter wtfjs
sur la ligne de commande. Cela ouvrira le manuel dans votre $PAGER
sélectionné. Sinon, vous pouvez continuer à lire ici.
La source est disponible ici : https://github.com/denysdovhan/wtfjs
Actuellement, il existe ces traductions de wtfjs :
Aide à traduire dans votre langue
Remarque : les traductions sont gérées par leurs traducteurs. Ils ne contiennent peut-être pas tous les exemples et les exemples existants peuvent être obsolètes.
[]
est égal ![]
true
n'est pas égal ![]
, mais pas égal []
aussiNaN
n'est pas un NaN
Object.is()
et ===
cas étranges[]
est vrai, mais pas true
null
est faux, mais pas false
document.all
est un objet, mais il n'est pas définiundefined
et Number
parseInt
est un méchanttrue
et false
NaN
est[]
et null
sont des objets0.1 + 0.2
String
constructor
__proto__
`${{Object}}`
try..catch
insidieux..catcharguments
et fonctions fléchéesNumber.toFixed()
affiche différents nombresMath.max()
inférieur à Math.min()
null
à 0
{}{}
n'est pas définiarguments
contraignantsalert
de l'enfersetTimeout
true
Juste pour le plaisir
— "Juste pour le plaisir : l'histoire d'un révolutionnaire accidentel" , Linus Torvalds
L'objectif principal de cette liste est de rassembler des exemples fous et d'expliquer leur fonctionnement, si possible. Tout simplement parce que c'est amusant d'apprendre quelque chose qu'on ne savait pas auparavant.
Si vous êtes débutant, vous pouvez utiliser ces notes pour approfondir votre connaissance de JavaScript. J'espère que ces notes vous motiveront à consacrer 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 les aspects inattendus de notre JavaScript bien-aimé.
Dans tous les cas, lisez simplement ceci. Vous allez probablement trouver quelque chose de nouveau.
️ Remarque : Si vous aimez lire ce document, pensez à soutenir l'auteur de cette collection.
// ->
est utilisé pour afficher le résultat d'une expression. Par exemple:
1 + 1 ; // -> 2
// >
signifie le résultat de console.log
ou une autre sortie. Par exemple:
console . log ( "hello, world!" ) ; // > hello, world!
//
est juste un commentaire utilisé pour les explications. Exemple:
// Assigning a function to foo constant
const foo = function ( ) { } ;
[]
est égal ![]
Le tableau est égal et non le tableau :
[ ] == ! [ ] ; // -> true
L'opérateur d'égalité abstrait convertit les deux côtés en nombres pour les comparer, et les deux côtés deviennent le nombre 0
pour différentes raisons. Les tableaux sont véridiques, donc à droite, l'opposé d'une valeur véridique est false
, qui est ensuite contrainte à 0
. Sur la gauche, cependant, un tableau vide est contraint à un nombre sans devenir d'abord un booléen, et les tableaux vides sont contraints à 0
, bien qu'ils soient véridiques.
Voici comment cette expression se simplifie :
+ [ ] == + ! [ ] ;
0 == + false ;
0 == 0 ;
true ;
Voir aussi []
est véridique, mais pas true
.
!
)true
n'est pas égal ![]
, mais pas égal []
aussi Array n'est pas égal à true
, mais Array n'est pas égal à true
également ; Array est égal à false
, et non Array est également égal à false
:
true == [ ] ; // -> false
true == ! [ ] ; // -> false
false == [ ] ; // -> true
false == ! [ ] ; // -> true
true == [ ] ; // -> false
true == ! [ ] ; // -> false
// According to the specification
true == [ ] ; // -> false
toNumber ( true ) ; // -> 1
toNumber ( [ ] ) ; // -> 0
1 == 0 ; // -> false
true == ! [ ] ; // -> false
! [ ] ; // -> false
true == false ; // -> false
false == [ ] ; // -> true
false == ! [ ] ; // -> true
// According to the specification
false == [ ] ; // -> true
toNumber ( false ) ; // -> 0
toNumber ( [ ] ) ; // -> 0
0 == 0 ; // -> true
false == ! [ ] ; // -> true
! [ ] ; // -> false
false == false ; // -> true
! ! "false" == ! ! "true" ; // -> true
! ! "false" === ! ! "true" ; // -> true
Considérez ceci étape par étape :
// true is 'truthy' and represented by value 1 (number), 'true' in string form is NaN.
true == "true" ; // -> false
false == "false" ; // -> false
// 'false' is not the empty string, so it's a truthy value
! ! "false" ; // -> true
! ! "true" ; // -> true
"b" + "a" + + "a" + "a" ; // -> 'baNaNa'
Il s'agit d'une blague à l'ancienne en JavaScript, mais remasterisée. Voici celui d'origine :
"foo" + + "bar" ; // -> 'fooNaN'
L'expression est évaluée comme 'foo' + (+'bar')
, ce qui convertit 'bar'
en non un nombre.
+
)NaN
n'est pas un NaN
NaN === NaN ; // -> false
La spécification définit strictement la logique derrière ce comportement :
- Si
Type(x)
est différent deType(y)
, renvoie false .- Si
Type(x)
est Nombre, alors
- Si
x
est NaN , renvoie false .- Si
y
est NaN , renvoie false .- … … …
— 7.2.14 Comparaison stricte d'égalité
Suivant 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 se présente lorsqu’au moins un opérande est NaN. Chaque NaN doit être comparé de manière désordonnée à tout, y compris lui-même.
— « Quelle est la raison pour laquelle toutes les comparaisons renvoient des valeurs fausses pour les valeurs NaN IEEE754 ? » chez StackOverflow
Object.is()
et ===
cas étranges Object.is()
détermine si deux valeurs ont la même valeur ou non. Cela fonctionne de manière similaire à l'opérateur ===
mais il existe quelques cas étranges :
Object . is ( NaN , NaN ) ; // -> true
NaN === NaN ; // -> false
Object . is ( - 0 , 0 ) ; // -> false
- 0 === 0 ; // -> true
Object . is ( NaN , 0 / 0 ) ; // -> true
NaN === 0 / 0 ; // -> false
Dans le jargon JavaScript, NaN
et NaN
ont la même valeur mais ils ne sont pas strictement égaux. NaN === NaN
étant faux est apparemment dû à des raisons historiques, il serait donc probablement préférable de l'accepter tel quel.
De même, -0
et 0
sont strictement égaux, mais ce n’est pas la même valeur.
Pour plus de détails sur NaN === NaN
, voir le cas ci-dessus.
Vous ne le croiriez pas, mais…
( ! [ ] + [ ] ) [ + [ ] ] +
( ! [ ] + [ ] ) [ + ! + [ ] ] +
( [ ! [ ] ] + [ ] [ [ ] ] ) [ + ! + [ ] + [ + [ ] ] ] +
( ! [ ] + [ ] ) [ ! + [ ] + ! + [ ] ] ;
// -> 'fail'
En brisant cette masse de symboles en morceaux, nous remarquons que le schéma suivant se produit souvent :
! [ ] + [ ] ; // -> 'false'
! [ ] ; // -> false
Nous essayons donc 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, mais le i
est délicat. Le i
in fail
est récupéré en générant la chaîne 'falseundefined'
et en récupérant l'élément sur l'index ['10']
.
Plus d'exemples :
+ ! [ ] // -> 0
+ ! ! [ ] // -> 1
! ! [ ] // -> true
! [ ] // -> false
[ ] [ [ ] ] // -> undefined
+ ! ! [ ] / + ! [ ] // -> Infinity
[ ] + { } // -> "[object Object]"
+ { } // -> NaN
[]
est vrai, mais pas true
Un tableau est une valeur véridique, cependant, il n'est pas égal à true
.
! ! [ ] // -> true
[ ] == true // -> false
Voici les liens vers les sections correspondantes de la spécification ECMA-262 :
!
)null
est faux, mais pas false
Même si null
est une valeur fausse, elle n'est pas égale à false
.
! ! null ; // -> false
null == false ; // -> false
En même temps, d'autres valeurs fausses, comme 0
ou ''
sont égales à false
.
0 == false ; // -> true
"" == false ; // -> true
L'explication est la même que pour l'exemple précédent. Voici le lien correspondant :
document.all
est un objet, mais il n'est pas défini
️ Cela fait partie de l'API du navigateur 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 undefined
.
document . all instanceof Object ; // -> true
typeof document . all ; // -> 'undefined'
Dans le même temps, document.all
n'est pas égal à undefined
.
document . all === undefined ; // -> false
document . all === null ; // -> false
Mais en même temps :
document . all == null ; // -> true
document.all
était autrefois un moyen d'accéder aux éléments du DOM, en particulier avec les anciennes versions d'IE. Bien qu'il n'ait jamais été un standard, il a été largement utilisé dans le code JS ancien. Lorsque la norme a progressé avec de nouvelles API (telles quedocument.getElementById
), cet appel d'API est devenu obsolète et le comité de normalisation a dû décider quoi en faire. En raison de sa large utilisation, ils ont décidé de conserver l'API mais d'introduire une violation délibérée de la spécification JavaScript. La raison pour laquelle il répond àfalse
lors de l'utilisation de la comparaison d'égalité stricte avecundefined
alors quetrue
lors de l'utilisation de la comparaison d'égalité abstraite est due à la violation délibérée de la spécification qui l'autorise explicitement.- "Fonctionnalités obsolètes - document.all" sur WhatWG - Spécification HTML - "Chapitre 4 - ToBoolean - Valeurs fausses" sur YDKJS - Types et grammaire
Number.MIN_VALUE
est le plus petit nombre supérieur à zéro :
Number . MIN_VALUE > 0 ; // -> true
Number.MIN_VALUE
est5e-324
, c'est-à-dire le plus petit nombre positif pouvant être représenté avec une précision flottante, c'est-à-dire qu'il est aussi proche que possible de zéro. Il définit la meilleure résolution que les flotteurs peuvent vous offrir.Désormais, la plus petite valeur globale est
Number.NEGATIVE_INFINITY
bien qu'elle ne soit pas vraiment numérique au sens strict.- "Pourquoi
0
est-il inférieur àNumber.MIN_VALUE
en JavaScript ?" chez StackOverflow
️ Un bug présent dans la V8 v5.5 ou inférieure (Node.js <=7)️
Vous connaissez tous l'ennuyeux undefined is not a function , mais qu'en est-il de cela ?
// Declare a class which extends null
class Foo extends null { }
// -> [Function: Foo]
new Foo ( ) instanceof null ;
// > TypeError: function is not a function
// > at … … …
Cela ne fait pas partie du cahier des charges. Il s'agit simplement d'un bug qui a maintenant été corrigé, il ne devrait donc pas y avoir de problème à l'avenir.
C'est la suite de l'histoire avec le bug précédent dans un environnement moderne (testé avec Chrome 71 et Node.js v11.8.0).
class Foo extends null { }
new Foo ( ) instanceof null ;
// > TypeError: Super constructor null of Foo is not a constructor
Ce n'est pas un bug car :
Object . getPrototypeOf ( Foo . prototype ) ; // -> null
Si la classe n'a pas de constructeur, appelez la chaîne de prototypes. Mais le parent n’a pas de constructeur. Juste au cas où, je précise que null
est un objet :
typeof null === "object" ;
Par conséquent, vous pouvez en hériter (même si dans le monde de la POO, de tels termes m'auraient battu). Vous ne pouvez donc pas appeler le constructeur nul. Si vous modifiez ce code :
class Foo extends null {
constructor ( ) {
console . log ( "something" ) ;
}
}
Vous voyez l'erreur :
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Et si vous ajoutez super
:
class Foo extends null {
constructor ( ) {
console . log ( 111 ) ;
super ( ) ;
}
}
JS renvoie une erreur :
TypeError: Super constructor null of Foo is not a constructor
Et si vous essayez d'ajouter deux tableaux ?
[ 1 , 2 , 3 ] + [ 4 , 5 , 6 ] ; // -> '1,2,34,5,6'
La concaténation se produit. Étape par étape, cela ressemble à ceci :
[ 1 , 2 , 3 ] +
[ 4 , 5 , 6 ] [
// call toString()
( 1 , 2 , 3 )
] . toString ( ) +
[ 4 , 5 , 6 ] . toString ( ) ;
// concatenation
"1,2,3" + "4,5,6" ;
// ->
( "1,2,34,5,6" ) ;
Vous avez créé un tableau avec 4 éléments vides. Malgré tout, vous obtiendrez un tableau avec trois éléments, à cause des virgules finales :
let a = [ , , , ] ;
a . length ; // -> 3
a . toString ( ) ; // -> ',,'
Les virgules de fin (parfois appelées « virgules finales ») peuvent être utiles lors de l'ajout de nouveaux éléments, paramètres ou propriétés au code JavaScript. Si vous souhaitez ajouter une nouvelle propriété, vous pouvez simplement ajouter une nouvelle ligne sans modifier la dernière ligne précédente si cette ligne utilise déjà une virgule de fin. Cela rend les différences de contrôle de version plus propres et l'édition du code peut être moins gênante.
— Virgules de fin chez MDN
L'égalité des tableaux est un monstre en JS, 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
Vous devez surveiller très attentivement les exemples ci-dessus ! Le comportement est décrit dans la section 7.2.15 Comparaison abstraite de l'égalité de la spécification.
undefined
et Number
Si nous ne transmettons aucun argument au constructeur Number
, nous obtiendrons 0
. La valeur undefined
est attribuée aux arguments formels lorsqu'il n'y a pas d'arguments réels, vous pouvez donc vous attendre à ce que Number
sans arguments prenne undefined
comme valeur de son paramètre. Cependant, lorsque nous passerons undefined
, nous obtiendrons NaN
.
Number ( ) ; // -> 0
Number ( undefined ) ; // -> NaN
Selon le cahier des charges :
n
+0
.n
être ? ToNumber(value)
.undefined
, ToNumber(undefined)
doit renvoyer NaN
.Voici la section correspondante :
argument
) parseInt
est un méchant parseInt
est célèbre pour ses bizarreries :
parseInt ( "f*ck" ) ; // -> NaN
parseInt ( "f*ck" , 16 ) ; // -> 15
Explication : cela se produit parce que parseInt
continuera à analyser caractère par caractère jusqu'à ce qu'il atteigne un caractère qu'il ne connaît pas. Le f
dans 'f*ck'
est le chiffre hexadécimal 15
.
Analyser Infinity
en 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 également prudent lors de l'analyse null
:
parseInt ( null , 24 ) ; // -> 23
Explication:
Il convertit
null
en chaîne"null"
et essaie de la convertir. Pour les bases 0 à 23, il n'y a aucun chiffre qu'il peut convertir, il renvoie donc NaN. A 24,"n"
, la 14ème lettre, est ajoutée au système numérique. En 31,"u"
, la 21ème lettre, est ajoutée et la chaîne entière peut être décodée. À 37 ans, il n'y a plus de jeu de chiffres valide pouvant être généré etNaN
est renvoyé.— "parseInt(null, 24) === 23… attends, quoi ?" chez StackOverflow
N'oubliez pas les octaux :
parseInt ( "06"