Uma lista de exemplos engraçados e complicados de JavaScript
JavaScript é uma ótima linguagem. Possui uma sintaxe simples, um grande ecossistema e, o mais importante, uma grande comunidade.
Ao mesmo tempo, todos sabemos que JavaScript é uma linguagem bastante engraçada com partes complicadas. Alguns deles podem rapidamente transformar nosso trabalho diário em um inferno, e alguns deles podem nos fazer rir alto.
A ideia original do WTFJS pertence a Brian Leroux. Esta lista é altamente inspirada em sua palestra “WTFJS” no dotJS 2012:
Você pode instalar este manual usando npm
. Basta executar:
$ npm install -g wtfjs
Você deve conseguir executar wtfjs
na linha de comando agora. Isso abrirá o manual no $PAGER
selecionado. Caso contrário, você pode continuar lendo aqui.
A fonte está disponível aqui: https://github.com/denysdovhan/wtfjs
Atualmente, existem estas traduções de wtfjs :
Ajude a traduzir para o seu idioma
Nota: As traduções são mantidas por seus tradutores. Eles podem não conter todos os exemplos e os exemplos existentes podem estar desatualizados.
[]
é igual ![]
true
não é igual ![]
, mas também não é igual []
NaN
não é um NaN
Object.is()
e ===
casos estranhos[]
é verdade, mas não é true
null
é falso, mas não false
document.all
é um objeto, mas é indefinidoundefined
e Number
parseInt
é um cara mautrue
e false
NaN
é[]
e null
são objetos0.1 + 0.2
String
constructor
__proto__
`${{Object}}`
try..catch
insidiosa..catcharguments
e funções de setaNumber.toFixed()
exibe números diferentesMath.max()
menor que Math.min()
null
com 0
{}{}
é indefinidoarguments
alert
do infernosetTimeout
true
Só por diversão
- “Só por diversão: a história de um revolucionário acidental” , Linus Torvalds
O objetivo principal desta lista é coletar alguns exemplos malucos e explicar como eles funcionam, se possível. Só porque é divertido aprender algo que não sabíamos antes.
Se você é iniciante, pode usar essas notas para se aprofundar no JavaScript. Espero que essas notas o motivem a passar mais tempo lendo as especificações.
Se você é um desenvolvedor profissional, pode considerar esses exemplos como uma ótima referência para todas as peculiaridades e arestas inesperadas do nosso amado JavaScript.
De qualquer forma, basta ler isto. Você provavelmente encontrará algo novo.
️ Nota: Se você gosta de ler este documento, considere apoiar o autor desta coleção.
// ->
é usado para mostrar o resultado de uma expressão. Por exemplo:
1 + 1 ; // -> 2
// >
significa o resultado de console.log
ou outra saída. Por exemplo:
console . log ( "hello, world!" ) ; // > hello, world!
//
é apenas um comentário usado para explicações. Exemplo:
// Assigning a function to foo constant
const foo = function ( ) { } ;
[]
é igual ![]
Matriz é igual, não matriz:
[ ] == ! [ ] ; // -> true
O operador de igualdade abstrata converte ambos os lados em números para compará-los, e ambos os lados se tornam o número 0
por diferentes razões. As matrizes são verdadeiras, portanto, à direita, o oposto de um valor verdadeiro é false
, que é então coagido para 0
. À esquerda, entretanto, um array vazio é forçado a um número sem se tornar um booleano primeiro, e os arrays vazios são forçados a 0
, apesar de serem verdadeiros.
Veja como esta expressão é simplificada:
+ [ ] == + ! [ ] ;
0 == + false ;
0 == 0 ;
true ;
Veja também []
é verdade, mas não é true
.
!
)true
não é igual ![]
, mas também não é igual []
Array não é igual true
, mas não Array também não é igual a true
; Array é igual false
, e não Array é igual a false
também:
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 este passo a passo:
// 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'
Esta é uma piada da velha escola em JavaScript, mas remasterizada. Aqui está o original:
"foo" + + "bar" ; // -> 'fooNaN'
A expressão é avaliada como 'foo' + (+'bar')
, que converte 'bar'
em não um número.
+
)NaN
não é um NaN
NaN === NaN ; // -> false
A especificação define estritamente a lógica por trás desse comportamento:
- Se
Type(x)
for diferente deType(y)
, retorne false .- Se
Type(x)
for Número, então
- Se
x
for NaN , retorne false .- Se
y
for NaN , retorne false .- … … …
— 7.2.14 Comparação estrita de igualdade
Seguindo a definição de NaN
do IEEE:
Quatro relações mutuamente exclusivas são possíveis: menor que, igual, maior que e não ordenada. O último caso surge quando pelo menos um operando é NaN. Todo NaN deve comparar o não ordenado com tudo, inclusive ele mesmo.
— “Qual é a justificativa para todas as comparações retornarem falsas para valores IEEE754 NaN?” no StackOverflow
Object.is()
e ===
casos estranhos Object.is()
determina se dois valores têm o mesmo valor ou não. Funciona de forma semelhante ao operador ===
, mas existem alguns casos estranhos:
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
Na linguagem JavaScript, NaN
e NaN
têm o mesmo valor, mas não são estritamente iguais. NaN === NaN
ser falso aparentemente se deve a razões históricas, então provavelmente seria melhor aceitá-lo como está.
Da mesma forma, -0
e 0
são estritamente iguais, mas não têm o mesmo valor.
Para obter mais detalhes sobre NaN === NaN
, consulte o caso acima.
Você não acreditaria, mas…
( ! [ ] + [ ] ) [ + [ ] ] +
( ! [ ] + [ ] ) [ + ! + [ ] ] +
( [ ! [ ] ] + [ ] [ [ ] ] ) [ + ! + [ ] + [ + [ ] ] ] +
( ! [ ] + [ ] ) [ ! + [ ] + ! + [ ] ] ;
// -> 'fail'
Ao quebrar essa massa de símbolos em pedaços, notamos que o seguinte padrão ocorre com frequência:
! [ ] + [ ] ; // -> 'false'
! [ ] ; // -> false
Então, tentamos adicionar []
a false
. Mas devido a uma série de chamadas de função internas ( binary + Operator
-> ToPrimitive
-> [[DefaultValue]]
) acabamos convertendo o operando certo em uma string:
! [ ] + [ ] . toString ( ) ; // 'false'
Pensando em uma string como um array podemos acessar seu primeiro caractere via [0]
:
"false" [ 0 ] ; // -> 'f'
O resto é óbvio, mas o i
é complicado. O i
in fail
é obtido gerando a string 'falseundefined'
e capturando o elemento no índice ['10']
.
Mais exemplos:
+ ! [ ] // -> 0
+ ! ! [ ] // -> 1
! ! [ ] // -> true
! [ ] // -> false
[ ] [ [ ] ] // -> undefined
+ ! ! [ ] / + ! [ ] // -> Infinity
[ ] + { } // -> "[object Object]"
+ { } // -> NaN
[]
é verdade, mas não é true
Uma matriz é um valor verdadeiro, porém não é igual a true
.
! ! [ ] // -> true
[ ] == true // -> false
Aqui estão links para as seções correspondentes na especificação ECMA-262:
!
)null
é falso, mas não false
Apesar de null
ser um valor falso, não é igual a false
.
! ! null ; // -> false
null == false ; // -> false
Ao mesmo tempo, outros valores falsos, como 0
ou ''
são iguais a false
.
0 == false ; // -> true
"" == false ; // -> true
A explicação é a mesma do exemplo anterior. Aqui está o link correspondente:
document.all
é um objeto, mas é indefinido
️ Isso faz parte da API do navegador e não funciona em um ambiente Node.js.️
Apesar de document.all
ser um objeto semelhante a um array e dar acesso aos nós DOM da página, ele responde à função typeof
como undefined
.
document . all instanceof Object ; // -> true
typeof document . all ; // -> 'undefined'
Ao mesmo tempo, document.all
não é igual a undefined
.
document . all === undefined ; // -> false
document . all === null ; // -> false
Mas ao mesmo tempo:
document . all == null ; // -> true
document.all
costumava ser uma forma de acessar elementos DOM, especialmente em versões antigas do IE. Embora nunca tenha sido um padrão, foi amplamente utilizado no código JS antigo. Quando o padrão progrediu com novas APIs (comodocument.getElementById
), essa chamada de API tornou-se obsoleta e o comitê padrão teve que decidir o que fazer com ela. Devido ao seu amplo uso, eles decidiram manter a API, mas introduziram uma violação intencional da especificação JavaScript. A razão pela qual ele responde comofalse
ao usar a Comparação Estrita de Igualdade comundefined
enquantotrue
ao usar a Comparação Abstrata de Igualdade é devido à violação intencional da especificação que explicitamente permite isso.— “Recursos obsoletos - document.all” em WhatWG - especificação HTML — “Capítulo 4 - ToBoolean - Valores falsos” em YDKJS - Tipos e gramática
Number.MIN_VALUE
é o menor número, maior que zero:
Number . MIN_VALUE > 0 ; // -> true
Number.MIN_VALUE
é5e-324
, ou seja, o menor número positivo que pode ser representado com precisão float, ou seja, é o mais próximo possível de zero. Ele define a melhor resolução que os flutuadores podem oferecer.Agora, o menor valor geral é
Number.NEGATIVE_INFINITY
embora não seja realmente numérico em sentido estrito.— “Por que
0
é menor queNumber.MIN_VALUE
em JavaScript?” no StackOverflow
️ Um bug presente na V8 v5.5 ou inferior (Node.js <=7)️
Todos vocês sabem sobre o irritante undefined is not a function , mas e quanto a isso?
// Declare a class which extends null
class Foo extends null { }
// -> [Function: Foo]
new Foo ( ) instanceof null ;
// > TypeError: function is not a function
// > at … … …
Isso não faz parte da especificação. É apenas um bug que já foi corrigido, então não deverá haver problemas com ele no futuro.
É a continuação da história com bug anterior em ambiente moderno (testado com Chrome 71 e Node.js v11.8.0).
class Foo extends null { }
new Foo ( ) instanceof null ;
// > TypeError: Super constructor null of Foo is not a constructor
Isso não é um bug porque:
Object . getPrototypeOf ( Foo . prototype ) ; // -> null
Se a classe não tiver construtor, a chamada da cadeia de protótipos. Mas no pai não tem construtor. Por precaução, vou esclarecer que null
é um objeto:
typeof null === "object" ;
Portanto, você pode herdar dele (embora no mundo OOP eu tivesse me derrotado para tais termos). Portanto, você não pode chamar o construtor nulo. Se você alterar este código:
class Foo extends null {
constructor ( ) {
console . log ( "something" ) ;
}
}
Você vê o erro:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
E se você adicionar super
:
class Foo extends null {
constructor ( ) {
console . log ( 111 ) ;
super ( ) ;
}
}
JS gera um erro:
TypeError: Super constructor null of Foo is not a constructor
E se você tentar adicionar dois arrays?
[ 1 , 2 , 3 ] + [ 4 , 5 , 6 ] ; // -> '1,2,34,5,6'
A concatenação acontece. Passo a passo, fica assim:
[ 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" ) ;
Você criou um array com 4 elementos vazios. Apesar de tudo, você obterá um array com três elementos, por causa das vírgulas finais:
let a = [ , , , ] ;
a . length ; // -> 3
a . toString ( ) ; // -> ',,'
As vírgulas finais (às vezes chamadas de "vírgulas finais") podem ser úteis ao adicionar novos elementos, parâmetros ou propriedades ao código JavaScript. Se quiser adicionar uma nova propriedade, você pode simplesmente adicionar uma nova linha sem modificar a última linha anterior, se essa linha já usar uma vírgula final. Isso torna as diferenças de controle de versão mais limpas e a edição do código pode ser menos problemática.
- Vírgulas finais no MDN
A igualdade de array é um monstro em JS, como você pode ver abaixo:
[ ] == '' // -> 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
Você deve observar com muito cuidado os exemplos acima! O comportamento é descrito na seção 7.2.15 Comparação abstrata de igualdade da especificação.
undefined
e Number
Se não passarmos nenhum argumento para o construtor Number
, obteremos 0
. O valor undefined
é atribuído a argumentos formais quando não há argumentos reais, portanto, você pode esperar que Number
sem argumentos receba undefined
como valor de seu parâmetro. Porém, quando passarmos undefined
, obteremos NaN
.
Number ( ) ; // -> 0
Number ( undefined ) ; // -> NaN
De acordo com a especificação:
n
+0
.n
ser? ToNumber(value)
.undefined
, ToNumber(undefined)
deve retornar NaN
.Aqui está a seção correspondente:
argument
) parseInt
é um cara mau parseInt
é famoso por suas peculiaridades:
parseInt ( "f*ck" ) ; // -> NaN
parseInt ( "f*ck" , 16 ) ; // -> 15
Explicação: Isso acontece porque parseInt
continuará analisando caractere por caractere até atingir um caractere que não conhece. O f
em 'f*ck'
é o dígito hexadecimal 15
.
Analisar Infinity
em número inteiro é 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
Tenha cuidado ao analisar null
também:
parseInt ( null , 24 ) ; // -> 23
Explicação:
Ele está convertendo
null
na string"null"
e tentando convertê-lo. Para bases de 0 a 23, não há numerais que possam ser convertidos, então ele retorna NaN. Aos 24,"n"
, a 14ª letra, é adicionada ao sistema numérico. Aos 31,"u"
, a 21ª letra, é adicionada e toda a string pode ser decodificada. Aos 37 anos não há mais nenhum conjunto numérico válido que possa ser gerado eNaN
é retornado.- “parseInt(null, 24) === 23… espere, o quê?” no StackOverflow
Não se esqueça dos octais:
parseInt ( "06"