Una lista de ejemplos de JavaScript divertidos y complicados
JavaScript es un gran lenguaje. Tiene una sintaxis simple, un gran ecosistema y, lo que es más importante, una gran comunidad.
Al mismo tiempo, todos sabemos que JavaScript es un lenguaje bastante divertido con partes complicadas. Algunos de ellos pueden convertir rápidamente nuestro trabajo diario en un infierno y otros pueden hacernos reír a carcajadas.
La idea original de WTFJS pertenece a Brian Leroux. Esta lista está muy inspirada en su charla "WTFJS" en dotJS 2012:
Puede instalar este manual usando npm
. Simplemente ejecuta:
$ npm install -g wtfjs
Ahora debería poder ejecutar wtfjs
en la línea de comando. Esto abrirá el manual en su $PAGER
seleccionado. De lo contrario, puedes seguir leyendo aquí.
La fuente está disponible aquí: https://github.com/denysdovhan/wtfjs
Actualmente, existen estas traducciones de wtfjs :
Ayuda para traducir a tu idioma
Nota: Las traducciones son mantenidas por sus traductores. Es posible que no contengan todos los ejemplos y que los ejemplos existentes estén desactualizados.
[]
es igual ![]
true
no es igual ![]
, pero tampoco es igual []
NaN
no es un NaN
Object.is()
y ===
casos extraños[]
es veraz, pero no true
null
es falso, pero no false
document.all
es un objeto, pero no está definidoundefined
y Number
parseInt
es un chico malotrue
y false
NaN
es[]
y null
son objetos0.1 + 0.2
String
constructor
__proto__
`${{Object}}`
try..catch
insidioso... atrapararguments
y funciones de flechaNumber.toFixed()
muestra diferentes númerosMath.max()
menos que Math.min()
null
con 0
{}{}
no está definidoarguments
vinculantesalert
del infiernosetTimeout
true
Sólo para travesura
— “Solo por diversión: la historia de un revolucionario accidental” , Linus Torvalds
El objetivo principal de esta lista es recopilar algunos ejemplos locos y explicar cómo funcionan, si es posible. Simplemente porque es divertido aprender algo que no sabíamos antes.
Si es principiante, puede utilizar estas notas para profundizar en JavaScript. Espero que estas notas lo motiven a dedicar más tiempo a leer la especificación.
Si es un desarrollador profesional, puede considerar estos ejemplos como una gran referencia para todas las peculiaridades y ventajas inesperadas de nuestro querido JavaScript.
En cualquier caso, lee esto. Probablemente encontrarás algo nuevo.
️ Nota: Si disfruta leyendo este documento, considere apoyar al autor de esta colección.
// ->
se utiliza para mostrar el resultado de una expresión. Por ejemplo:
1 + 1 ; // -> 2
// >
significa el resultado de console.log
u otra salida. Por ejemplo:
console . log ( "hello, world!" ) ; // > hello, world!
//
es solo un comentario usado para explicaciones. Ejemplo:
// Assigning a function to foo constant
const foo = function ( ) { } ;
[]
es igual ![]
La matriz es igual, no la matriz:
[ ] == ! [ ] ; // -> true
El operador de igualdad abstracta convierte ambos lados en números para compararlos, y ambos lados se convierten en el número 0
por diferentes razones. Las matrices son verdaderas, por lo que a la derecha, lo opuesto a un valor verdadero es false
, que luego se fuerza a 0
. A la izquierda, sin embargo, una matriz vacía se convierte en un número sin convertirse primero en booleana, y las matrices vacías se convierten en 0
, a pesar de ser veraces.
Así es como se simplifica esta expresión:
+ [ ] == + ! [ ] ;
0 == + false ;
0 == 0 ;
true ;
Ver también []
es veraz, pero no true
.
!
)true
no es igual ![]
, pero tampoco es igual []
Array no es igual a true
, pero no Array no es igual a true
también; La matriz es igual a false
, no la matriz también es igual a 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
Considere esto paso a paso:
// 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'
Este es un chiste de la vieja escuela en JavaScript, pero remasterizado. Aquí está el original:
"foo" + + "bar" ; // -> 'fooNaN'
La expresión se evalúa como 'foo' + (+'bar')
, lo que convierte 'bar'
en no un número.
+
)NaN
no es un NaN
NaN === NaN ; // -> false
La especificación define estrictamente la lógica detrás de este comportamiento:
- Si
Type(x)
es diferente deType(y)
, devuelve false .- Si
Type(x)
es Número, entonces
- Si
x
es NaN , devuelve falso .- Si
y
es NaN , devuelve falso .- …… …
— 7.2.14 Comparación estricta de igualdad
Siguiendo la definición de NaN
del IEEE:
Son posibles cuatro relaciones mutuamente excluyentes: menor que, igual, mayor que y desordenada. El último caso surge cuando al menos un operando es NaN. Cada NaN se comparará desordenadamente con todo, incluido él mismo.
- "¿Cuál es el motivo por el que todas las comparaciones arrojan resultados falsos para los valores NaN IEEE754?" en StackOverflow
Object.is()
y ===
casos extraños Object.is()
determina si dos valores tienen el mismo valor o no. Funciona de manera similar al operador ===
pero hay algunos casos extraños:
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
En la jerga de JavaScript, NaN
y NaN
tienen el mismo valor pero no son estrictamente iguales. NaN === NaN
sea falso aparentemente se debe a razones históricas, por lo que probablemente sería mejor aceptarlo tal como está.
De manera similar, -0
y 0
son estrictamente iguales, pero no tienen el mismo valor.
Para obtener más detalles sobre NaN === NaN
, consulte el caso anterior.
No lo creerías, pero...
( ! [ ] + [ ] ) [ + [ ] ] +
( ! [ ] + [ ] ) [ + ! + [ ] ] +
( [ ! [ ] ] + [ ] [ [ ] ] ) [ + ! + [ ] + [ + [ ] ] ] +
( ! [ ] + [ ] ) [ ! + [ ] + ! + [ ] ] ;
// -> 'fail'
Al dividir esa masa de símbolos en pedazos, notamos que el siguiente patrón ocurre con frecuencia:
! [ ] + [ ] ; // -> 'false'
! [ ] ; // -> false
Entonces intentamos agregar []
a false
. Pero debido a una serie de llamadas a funciones internas ( binary + Operator
-> ToPrimitive
-> [[DefaultValue]]
), terminamos convirtiendo el operando correcto en una cadena:
! [ ] + [ ] . toString ( ) ; // 'false'
Pensando en una cadena como una matriz, podemos acceder a su primer carácter mediante [0]
:
"false" [ 0 ] ; // -> 'f'
El resto es obvio, pero la i
es complicada. La i
en caso de fail
se captura generando la cadena 'falseundefined'
y capturando el elemento en el índice ['10']
.
Más ejemplos:
+ ! [ ] // -> 0
+ ! ! [ ] // -> 1
! ! [ ] // -> true
! [ ] // -> false
[ ] [ [ ] ] // -> undefined
+ ! ! [ ] / + ! [ ] // -> Infinity
[ ] + { } // -> "[object Object]"
+ { } // -> NaN
[]
es veraz, pero no true
Una matriz es un valor verdadero, sin embargo, no es igual a true
.
! ! [ ] // -> true
[ ] == true // -> false
Aquí hay enlaces a las secciones correspondientes en la especificación ECMA-262:
!
)null
es falso, pero no false
A pesar de que null
es un valor falso, no es igual a false
.
! ! null ; // -> false
null == false ; // -> false
Al mismo tiempo, otros valores falsos, como 0
o ''
son iguales a false
.
0 == false ; // -> true
"" == false ; // -> true
La explicación es la misma que para el ejemplo anterior. Aquí tenéis el enlace correspondiente:
document.all
es un objeto, pero no está definido
️ Esto es parte de la API del navegador y no funcionará en un entorno Node.js.️
A pesar de que document.all
es un objeto similar a una matriz y brinda acceso a los nodos DOM en la página, responde a la función typeof
como undefined
.
document . all instanceof Object ; // -> true
typeof document . all ; // -> 'undefined'
Al mismo tiempo, document.all
no es igual a undefined
.
document . all === undefined ; // -> false
document . all === null ; // -> false
Pero al mismo tiempo:
document . all == null ; // -> true
document.all
solía ser una forma de acceder a elementos DOM, en particular con versiones antiguas de IE. Si bien nunca ha sido un estándar, se usó ampliamente en el código JS antiguo. Cuando el estándar avanzó con nuevas API (comodocument.getElementById
), esta llamada a la API quedó obsoleta y el comité de estándares tuvo que decidir qué hacer con ella. Debido a su amplio uso, decidieron mantener la API pero introdujeron una violación intencionada de la especificación de JavaScript. La razón por la que respondefalse
cuando se usa la Comparación de igualdad estricta conundefined
mientras que estrue
cuando se usa la Comparación de igualdad abstracta se debe a la violación intencional de la especificación que lo permite explícitamente.— “Características obsoletas - document.all” en WhatWG - Especificaciones HTML — “Capítulo 4 - ToBoolean - Valores falsos” en YDKJS - Tipos y gramática
Number.MIN_VALUE
es el número más pequeño, que es mayor que cero:
Number . MIN_VALUE > 0 ; // -> true
Number.MIN_VALUE
es5e-324
, es decir, el número positivo más pequeño que se puede representar con precisión flotante, es decir, lo más cerca que se puede llegar a cero. Define la mejor resolución que los flotadores pueden brindarle.Ahora el valor más pequeño general es
Number.NEGATIVE_INFINITY
aunque en realidad no es numérico en sentido estricto.- "¿Por qué
0
es menor queNumber.MIN_VALUE
en JavaScript?" en StackOverflow
️ Un error presente en V8 v5.5 o inferior (Node.js <=7)️
Todos ustedes saben acerca del molesto indefinido no es una función , pero ¿qué pasa con esto?
// Declare a class which extends null
class Foo extends null { }
// -> [Function: Foo]
new Foo ( ) instanceof null ;
// > TypeError: function is not a function
// > at … … …
Esto no es parte de la especificación. Es solo un error que ya se ha solucionado, por lo que no debería haber ningún problema en el futuro.
Es una continuación de la historia con un error anterior en un entorno moderno (probado con Chrome 71 y Node.js v11.8.0).
class Foo extends null { }
new Foo ( ) instanceof null ;
// > TypeError: Super constructor null of Foo is not a constructor
Esto no es un error porque:
Object . getPrototypeOf ( Foo . prototype ) ; // -> null
Si la clase no tiene constructor, la llamada desde la cadena de prototipos. Pero en el padre no hay constructor. Por las dudas, aclararé que null
es un objeto:
typeof null === "object" ;
Por lo tanto, puedes heredar de él (aunque en el mundo de la POO por esos términos me habría ganado). Entonces no puedes llamar al constructor nulo. Si cambia este código:
class Foo extends null {
constructor ( ) {
console . log ( "something" ) ;
}
}
Ves el error:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Y si agregas super
:
class Foo extends null {
constructor ( ) {
console . log ( 111 ) ;
super ( ) ;
}
}
JS arroja un error:
TypeError: Super constructor null of Foo is not a constructor
¿Qué pasa si intentas agregar dos matrices?
[ 1 , 2 , 3 ] + [ 4 , 5 , 6 ] ; // -> '1,2,34,5,6'
La concatenación ocurre. Paso a paso, se ve así:
[ 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" ) ;
Has creado una matriz con 4 elementos vacíos. A pesar de todo, obtendrás una matriz con tres elementos, debido a las comas finales:
let a = [ , , , ] ;
a . length ; // -> 3
a . toString ( ) ; // -> ',,'
Las comas finales (a veces llamadas "comas finales") pueden resultar útiles al agregar nuevos elementos, parámetros o propiedades al código JavaScript. Si desea agregar una nueva propiedad, simplemente puede agregar una nueva línea sin modificar la última línea anterior si esa línea ya usa una coma final. Esto hace que las diferencias de control de versiones sean más limpias y la edición de código pueda ser menos problemática.
— Comas al final en MDN
La igualdad de matrices es un monstruo en JS, como puedes ver a continuación:
[ ] == '' // -> 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
¡Debe observar con mucha atención los ejemplos anteriores! El comportamiento se describe en la sección 7.2.15 Comparación de igualdad abstracta de la especificación.
undefined
y Number
Si no pasamos ningún argumento al constructor Number
, obtendremos 0
. El valor undefined
se asigna a argumentos formales cuando no hay argumentos reales, por lo que se podría esperar que Number
sin argumentos tome undefined
como valor de su parámetro. Sin embargo, cuando pasamos undefined
, obtendremos NaN
.
Number ( ) ; // -> 0
Number ( undefined ) ; // -> NaN
Según la especificación:
n
+0
.n
? ToNumber(value)
.undefined
, ToNumber(undefined)
debería devolver NaN
.Aquí está la sección correspondiente:
argument
) parseInt
es un chico malo parseInt
es famoso por sus peculiaridades:
parseInt ( "f*ck" ) ; // -> NaN
parseInt ( "f*ck" , 16 ) ; // -> 15
Explicación: Esto sucede porque parseInt
continuará analizando carácter por carácter hasta encontrar un carácter que no conoce. La f
en 'f*ck'
es el dígito hexadecimal 15
.
Analizar Infinity
a un número entero es algo...
//
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
Tenga cuidado al analizar null
también:
parseInt ( null , 24 ) ; // -> 23
Explicación:
Está convirtiendo
null
en la cadena"null"
e intentando convertirlo. Para las bases del 0 al 23, no hay números que pueda convertir, por lo que devuelve NaN. A los 24, se añade"n"
, la decimocuarta letra, al sistema numérico. En 31, se agrega"u"
, la letra número 21, y se puede decodificar toda la cadena. A partir del 37 ya no hay ningún conjunto de números válido que pueda generarse y se devuelveNaN
.— “parseInt(null, 24) === 23… espera, ¿qué?” en StackOverflow
No te olvides de los octales:
parseInt ( "06"