Skip to content

Latest commit

 

History

History
1729 lines (1199 loc) · 55.5 KB

README-pt-br.md

File metadata and controls

1729 lines (1199 loc) · 55.5 KB

What the f*ck JavaScript?

WTFPL 2.0 NPM version

Uma lista de exemplos engraçados e truques com JavaScript

JavaScript é uma excelente linguagem. Ela tem uma sintaxe simples, um ecossistema grande e, o mais importante, uma grande comunidade.

Ao mesmo tempo, todos nós sabemos que o JavaScript é uma linguagem engraçada com várias partes complicadas. Algumas delas porem rapidamente transformar seu trabalho em um inferno, e outras podem nos fazer gargalhar.

A ideia original para o WTFJS é do Brian Leroux. Essa lista é inspirada por sua talk “WTFJS” no dotJS 2012:

dotJS 2012 - Brian Leroux - WTFJS

Node Packaged Manuscript

Você pode instalar esse manual usando o npm. É só rodar o comando:

$ npm install -g wtfjs

Você poderá rodar wtfjs na sua linha de comando. Esse comando vai abrir o manual na sua $PAGER selecionada ou você pode continuar lendo aqui mesmo.

O código-fonte está disponível aqui https://github.com/denysdovhan/wtfjs.

Traduções

Atualmente, temos essas traduções disponíveis de wtfjs:

Solicite outra tradução

Table of Contents

💪🏻 Motivação

Just for fun

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

O objetivo dessa lista era coletar alguns exemplos malucos e explicar como eles funcionam, se possível. Apenas porque é legal aprender algo que nós não conhecemos.

Se você é um iniciante, você poderá utilizar esses pontos para se aprofundar no JavaScript. Eu espero que esses pontos te motivem em gastar um pouco mais de tempo lendo as especificações.

Se você já é um desenvolvedor profissional, você pode considerar esses exemplos como uma excelente referência para todos as peculiaridades e pontos inesperados do nosso amado JavaScript.

Em todo caso, leia. Você provavelmemte irá aprender algo novo.

✍🏻 Notação

// -> é utilizado para mostrar o resultado de uma expressão. Por exemplo:

1 + 1; // -> 2

// > significa o resultado de console.log ou qualquer outra saída. Por exemplo:

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

// são apenas comentários para as explicações. Exemplo:

// Atribuindo uma função para a constante foo
const foo = function() {};

👀 Exemplos

[] é igual a ![]

Array é igual a not array:

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

💡 Explicação:

O operador abstrato de igualdade converte os dois lados em números para compará-los, e os dois lados se tornam 0 por razões diferentes. Arrays são verdadeiros (truthy), então na direita, o oposto de um valor verdadeiro é false, o que é coagido para 0. Na esquerda, todavia, um array vazio é coagido para um número sem se tornar um booleano (boolean) primeiro, e arrays vazios sempre forçados para 0, apesar de serem verdadeiros.

Aqui está uma simplificação dessa expressão:

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

Veja também [] is truthy, but not true.

true não é igual a ![], nem igual a [] também

Array não é igual a true, mas not Array também não é igual a true' Array é igual a false, not Array é igual a false também:

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

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

💡 Explicação:

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

// De acordo com a especificação

true == []; // -> false

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

1 == 0; // -> false

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

![]; // -> false

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

// De acordo com a especificação

false == []; // -> true

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

0 == 0; // -> true

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

![]; // -> false

false == false; // -> true

true é false

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

💡 Explicação:

Considere esse passo-a-passo:

// true é 'truthy' e representado pelo valor 1 (number), 'true' como string é NaN.
true == "true"; // -> false
false == "false"; // -> false

// 'false' não é uma string vazia, então ele é um valor verdadeiro (truthy)
!!"false"; // -> true
!!"true"; // -> true

baNaNa

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

Essa é uma piada antiga no JavaScript, mas remasterizada. Aqui está a forma original:

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

💡 Explicação:

A expressão é avaliada como 'foo' + (+'bar'), o que converte bar para um "não número" (NaN - Not a Number).

NaN não é um NaN

NaN === NaN; // -> false

💡 Explicação:

A especificação define estritamente a lógica por trás desse comportamento:

  1. Se Type(x) é diferente de Type(y), retorne false.
  2. Se Type(x) é um Number, então
    1. Se x é um NaN, retorne false.
    2. Se y é um NaN, retorne false.
    3. … … …

7.2.14 Strict Equality Comparison

Seguindo a definição de NaN do IEEE:

Quatro relações de exclusões mútuas são possíveis: menor que (less than), igual (equal), maior que (greater than), e não ordenado (unordered). O último caso surge quando, pelo menos, um operador é um NaN. Todo NaN deve comprarar não ordenado (unordered) com tudo, incluindo a si mesmo.

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

É uma falha

Você não vai acreditar, mas ...

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

💡 Explicação:

Quando nós quebramos esses símbolos em pedaços, percebemos que o esse padrão se repete com frequência:

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

Então nós tentamos adicionar [] para false. Mas devido a um número interno de chamadas de função (binary + Operator -> ToPrimitive -> [[DefaultValue]]) nós acabamos convertendo o operador da direita para uma string:

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

Pensando em uma string como um array nós conseguimos acessar seu primeiro caractere usando [0]:

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

O resto é óbvio, mas o i é ardiloso. O i em fail é pego através da geração da string 'falseundefined' e pegando o element no índice ['10']

[] é verdadeiro, mas não true

Um array é um valor verdadeiro (truthy), porém, não é igual a true.

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

💡 Explicação:

Aqui estão links das seções correspondentes especificação do ECMA-262:

null é falso, mas não false

Apesar do fato que null é um valor falso (falsy), ele não é igual a false.

!!null; // -> false
null == false; // -> false

Ao mesmo tempo, outro valor falso (falsy), como 0 ou '' são iguais a false.

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

💡 Explicação:

A explicação é a mesma dos exemplos anteriores. Aqui está o link correspondente:

document.all é um objeto (object), mas é indefinido (undefined)

⚠️ Esta é a parte da API Browser e não irá funcionar em um ambiente com Node.js - apenas em navegadores ⚠️

Apesar de document.allser um objeto parecido com um array, ele dá acesso aos nós do DOM na página, e responde comoundefinedna funçãotypeof`.

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

💡 Explicação:

document.all é usado como uma maneira de acessar todos os elementos do DOM, em particular com versões legadas do IE. Mesmo nunca tendo se tornado um padrão, foi amplamente usado nas eras antigas do JS. Quando o padrão progrediu com novas APIs (como document.getElementById) essa API (document.all) se tornou obsoleta e o comitê padrão teve que decidir o que fazer com ela. Por conta do amplo uso eles decidiram deixar a API mas introduziram uma violação intencional da especificação do JavaScript. A razão que ele retorna como false quando usamos o Comparador Estrito de Igualdade com undefined e true quando usamos o Comparador Abstrado de Igualdade é devido a essa violação intencional que explicitamente permite isso.

“Obsolete features - document.all” em WhatWG - HTML spec

“Chapter 4 - ToBoolean - Falsy values” em YDKJS - Types & Grammar

Valor mínimo é maior que zero

Number.MIN_VALUE é o menor número, que ainda é maior que zero:

Number.MIN_VALUE > 0; // -> true

💡 Explicação:

Number.MIN_VALUE é igual a 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. Isso define a melhor resolução que pontos flutuantes (floats) podem fornecer.

Agora, o menor valor geral é Number.NEGATIVE_INFINITY, embora ele não seja realmente numérico em um senso estrito.

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

function não é uma function

⚠️ Um bug presenta na V8 v5.5 or anterior (Node.js <=7) ⚠️

Todos vocês conhecem a chatice de undefined is not a function, mas e quanto a isso?

// Declare uma classe que extende de null
class Foo extends null {}
// -> [Function: Foo]

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

💡 Explicação:

Isto não é parte da especificação. É apenas um bug que já foi arrumado, então isso não deverá ser um problema no futuro.

Somando arrays

E se você tentar somar dois arrays?

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

💡 Explicação:

A concatenação ocorre. Passo-a-passo, ela ocorre mais ou menos 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");

Vírgulas finais em arrays

Você criou um array com 4 elementos vazios. Apesar disso, você terá um array com três elementos, por conta das vírgulas finais (trailing commas):

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

💡 Explicação:

Trailing commas (também chamadas de "final commas", ou em português, "vírgulas finais") são úteis quando você adiciona novos elementos, parâmetros ou propriedades em um código JS. Caso se você quer adicionar uma nova propriedade, você pode simplesmente adicionar uma nova linha sem modificar a anterior se ela já utiliza uma trailling comma. Isso faz com que os diffs no versionamento de código sejam mais limpos, e também a edição do código menos problemática.

Trailing commas no MDN

Igualdade entre arrays é um monstro

Igualdade de arrays é um monstro no 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

💡 Explicação:

Você deve observar bem cautelosamente os exemplos acima! O comportamento é descrito na seção 7.2.13 Abstract Equality Comparison da especificação.

undefined e Number

Se nós não passarmos nenhum argumento em um construtor Number, nós teremos 0 como retorno. O valor undefined é atribuído em argumentos formais quando não não existem argumentos, então você deve esperar que Number sem argumentos receba undefined como um valor dos seus parâmetros. Todavia, quando passamos undefined, o retorno será NaN.

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

💡 Explicação:

De acordo com a especificação:

  1. Se nenhum argumento for passado na chamada da função, n será +0.
  2. Se não, n será ? ToNumber(value).
  3. Em caso de undefined, ToNumber(undefined) deve retornar NaN.

Aqui está a seção correspondente:

parseInt é um vilão

parseInt é famoso por suas peculiaridades:

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

💡 Explicação:

Isso acontece porque parseInt vai continuar parseando caractere por caractere até que ele atinja um caractere desconhecido. O f em f*ck é o dígito hexadecimal 15.

Se você parsear Infinity para um inteiro…

//
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 quando parsear um null também:

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

💡 Explicação:

Ele converte null para uma string "null" e tenta fazer o parse. Para raízes de 0 a 23, não existem numerais que ele possa converter, então ele retorna NaN. Em 24, "n", a 14ª letra, é adicionada ao sistema numérico. Em 31, "u", a 21ª letra, é adicionada e a string inteira poderá ser decodificada. Em 37 onde não existe mais nenhum numeral válido definido que poderá ser gerado, o retorno é NaN.

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

Não se esqueça dos octals:

parseInt("06"); // 6
parseInt("08"); // 8 se suporta ECMAScript 5
parseInt("08"); // 0 se não suporta ECMAScript 5

💡 Explicação:

Se uma string de entrada começa com "0", a raiz é oito (octal) ou 10 (decimal). A raiz que é escolhida dependerá da implementação. O ECMAScript 5 define que a 10 (decimal) é utilizada, mas nem todos os navegadores suportam isso ainda. Por essa razão sempre deixe explícito qual será a raiz utilizada quando você usar o parseInt.

parseInt sempre converte a entrada para string:

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

Tenha cuidado quando tentar fazer o parse de valores floating ponts (pontos flutuantes)

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

💡 Explicação:

ParseInt recebe uma string como argumento e retorna um inteiro da raiz específica. ParseInt também remove tudo depois e incluindo o primeiro non-digit (não dígito) no parâmetro como string. 0.000001 é convertido para a string "0.000001" e o parseInt retorna 0. Quando 0.0000001 é convertido para uma string ele é tratado como "1e-7" e, portanto, parseInt retorna 1. 1/1999999 é interpretado como 5.00000250000125e-7 e o parseInt retorna 5.

Matemática com true e false

Vamos fazer algumas contas:

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

Hmmm… 🤔

💡 Explicação:

Podemos forçar valores números com o construtor Number. É bem óbvio que true será forçado para 1:

Number(true); // -> 1

O operador unário soma (i++) tenta converter o valor para um número. Ele pode converter representações de inteiros e flutuantes em strings, bem como os valores que não são stings, como true, false e null. Se ele não conseguir parsear um valor particular, então será avaliado como NaN. Isso significa que nós podemos forçar true para 1 facilmente:

+true; // -> 1

Quando você realiza uma adição ou uma multiplicacão, o método ToNumber é invocado. De acordo com a especificação, esse método retorna:

Se argument é true, o retorno será 1. Se argumento é false, o retorno será 0.

Por isso podemos adicionar valores booleanos (boolean) como números regulares e obtermos os resultados corretos.

Seções correspondentes:

Comentários HTML são válidos no JavaScript

Você ficará impressionado, mas <!-- (sintaxe de comentários do HTML) são comentários válidos no JavaScript.

// comentário válido
<!-- comentário válido também

💡 Explicação:

Impressionado? Comentários HTML se destinavam a permitir que navegadores que não interpretavam a tag <script> fossem degradados normalmente. Esses browsers, e.x. Netscape 1.x, não são mais populares. Portanto, não precisamos mais colocar comentários HTML em suas tags script.

Como o Node.js é baseado na V8, comentários HTML são suportados pela runtime do Node.js também. Além disso, eles fazem parte da especificação:

NaN não é um número

O tipo NaN é um 'number':

typeof NaN; // -> 'number'

💡 Explicação:

Explicações de como os operadores typeof e instanceof funcionam:

[] e null são objetos

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

// contudo
null instanceof Object; // false

💡 Explicação:

O comportamento do operador typeof é definido nessa seção da especificação:

De acordo com a especificação, o operador typeof retorna uma string de acordo com a Table 35: typeof Operator Results. Para null, objetos exóticos comuns e exóticos não padronizados, que não implementam [[Call]], o retorno será a string "object".

Todavia, você poderá verificar o tipo de um objeto usando o método toString.

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

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

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

Aumentando números magicamente

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

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

💡 Explicação:

Isso é causado pelo padrão IEEE 754-2008 para Binary Floating-Point Arithmetic (Aritmética de binários de ponto flutuante). Nessa escala, ele arredonda para o número par mais próximo. Leia mais:

Precisão de 0.1 + 0.2

Uma piada bastante conhecida. Uma adição de 0.1 e 0.2 é mortalmente precisa:

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

💡 Explicação:

A responsta para a pergunta ”Is floating point math broken?” no StackOverflow:

As constantes 0.2 and 0.3 no seu programa serão também aproximações dos seus valores verdadeiros. Isso ocorre quando o double mais próximo de 0.2 é maior que o número racional 0.2, mas o double mais próximo de 0.3 é menor que o número racional 0.3. A soma de 0.1 e 0.2 acaba sendo maior que o número racional 0.3, e, portanto, discorda da constante em seu código.

Esse problema é tão conhecido que existe um website chamado 0.30000000000000004.com. Isso ocorre em todas as linguagens que utilizam floating-point math (matemática de ponto flutuante), não apenas no JavaScript.

Patching numbers

Você pode adicionar seus próprios métodos em objetos como 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

💡 Explicação:

Obviamente você pode extender o objeto Number como qualquer outro no JavaScript, contudo, não é recomendado se o comportamento do método definido não for parte da especificação. Aqui está a lista de propriedades do Number:

Comparação de três números

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

💡 Explicação:

Por que isso funciona assim? Bem, o problema está na primeira parte da expressão. Aqui está como isso funciona:

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

Nós podemos resolver isso com o operador Maior ou igual que (>=);

3 > 2 >= 1; // true

Leia mais sobre os operadores Relacionais na especificação:

Matemática engraçada

Geralmente os resultados de operações aritméticas em JavaScript podem ser inespererados. Considere esses exemplos:

 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

💡 Explicação:

O que está acontecendo com os primeiros quatro exemplos? Aqui está uma tabela para entender a soma no JavaScript:

Number  + Number  -> adição
Boolean + Number  -> adição
Boolean + Boolean -> adição
Number  + String  -> concatenação
String  + Boolean -> concatenação
String  + String  -> concatenação

E quanto aos outros exempos? Os métodos ToPrimitive e ToString estão sendo chamados implicitamente por [] e {} antes da adição. Leia mais sobre o processo de evaluação na especificação:

Soma de RegExps

Você sabia que você pode somar números dessa forma?

// Adicione um método toString
RegExp.prototype.toString =
  function() {
    return this.source;
  } /
  7 /
  -/5/; // -> 2

💡 Explicação:

Strings não são instâncias String

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

💡 Explicação:

O construtor String retorna uma string:

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

Vamos tentar com um new:

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

Objeto? O que é isso?

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

Mais informações sobre o construtor String na especificação:

Chamando funções com backticks

Vamos declarar uma função que irá logar todos os parâmetros no console:

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

Sem dúvida, você sabe que uma função pode ser chamada assim:

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

Mas você sabia que você pode chamar qualquer função usando backticks (crases)?

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

💡 Explicação:

Bom, isso não é uma mágica se você está familiarizado com Tagged template literals. No exemplo acima, a função f é uma tag para template literal. Tags antes do template literal permitem que você faça o parse do template literals com uma função. O primeiro argumento de uma função tag contém um array de valores em string. O restante dos argumentos são relacionados às expressões. Exemplo:

function template(strings, ...keys) {
  // faça algo com as strings e as chaves...
}

Esta é a mágica por trás de uma lib famosa, chamada 💅 styled-components - que é bem conhecida na comunidade React.

Link para a especificação:

Call call call

Achado por @cramforce

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

💡 Explicação:

Atenção, isso vai explodir sua mente! Tente reproduzir esse código na sua cabeça: estamos aplicando o método call usando o método apply. Leia mais:

Uma propriedade constructor

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

💡 Explicação:

Vamos considerar esse exemplo passo-a-passo:

// Declare uma nova constante, com um valor 'constructor' em string
const c = "constructor";

// c é uma string
c; // -> 'constructor'

// Recuperando o construtor da string
c[c]; // -> [Function: String]

// Recuperando o construtor do construtor
c[c][c]; // -> [Function: Function]

// Chame a função e passe o corpo
// de uma nova função como argumento
c[c][c]('console.log("WTF?")'); // -> [Function: anonymous]

// E então chame essa função anônima
// O Resultado será um log no console com a string 'WTF?'
c[c][c]('console.log("WTF?")')(); // > WTF?

Um Object.prototype.constructor retorna a referência da função construtora Object que criou a instância do objeto. Em casos com strings ele é String, em casos com números ele é um Number, e assim por diante.

Objeto como uma chave de uma propriedade de objeto

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

💡 Explicação:

Por que isso funciona assim? Aqui estamos usando uma Computed property name. Quando você passa um objeto dentro desses colchetes ({ }), ele força o objeto para uma string, e então temos a chave '[object Object]' com o valor {}.

Nós podemos fazer o "brackets hell" dessa forma:

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

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

Leia mais sobre object literals aqui:

Acessando protótipos com __proto__

Como nós sabemos, os primitivos não tem protótipos. Contudo, se nós tentarmos recuperar o valor de um __proto__ para primitivos, teremos o seguinte retorno:

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

💡 Explicação:

Isso acontece porque quando algo não possui um protótipo, ele será envolvido em um objeto usando o método ToObject. Então, seguindo essa linha:

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

Aqui temos mais informações sobre __proto__:

`${{Object}}`

Qual é o resultado da expressão abaixo?

`${{ Object }}`;

A resposta é:

// -> '[object Object]'

💡 Explicação:

Nós definimos um objeto com a propriedade Object usando a Shorthand property notation (notação curta de propriedade).

{
  Object: Object;
}

Então nós passamos esse objeto para o template literal, e o método toString chama por aquele objeto. Por isso temos a string '[object Object]'.

Desestruturação com valores padrão

Considere o exemplo:

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

O exemplo acima é uma excelente tarefa para uma entrevista. Qual é o valor de y? A resposta é:

// -> 1

💡 Explicação:

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

Com o exemplo acima:

  1. Nós declaramos x sem nenhum valor, então ele é undefined.
  2. Então nós empacotamos o valor de x dentro da propriedade x do objeto.
  3. Depois nós extraímos o valor de x usando a desestruturação e queremos atribuí-lo para y. Se o valor não for definido, então usaremos 1 como default.
  4. Retornarmos o valor de y.

Pontos e dispersão

Exemplos interessantes podem ser compostos com spreading (dispersão) de arrays. Considere o seguinte:

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

💡 Explicação:

Por que 3? Quando utilizamos o spread operator, o método @@iterator é chamado, e o iterator retornado é utilizado para obter os valores para ser iterado. O iterador padrão para string dispersa uma string em caracteres. Depois de dispersar, nós empacotamos esses valores dentro de um array. E então dispersamos esse array novamente e empacotamos de volta em um array.

Uma string de '...' consistem em três caracteres de ., então o tamanho do array resultante será 3.

Agora, detalhadamente:

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

Obviamente, nós podemos dispersar e envolver elementos de um array quantas vezes quisermos:

[...'...']                 // -> [ '.', '.', '.' ]
[...[...'...']]            // -> [ '.', '.', '.' ]
[...[...[...'...']]]       // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]]  // -> [ '.', '.', '.' ]
// e assim vai ...

Rótulos

Poucos programadores conhecem sobre rótulos no JavaScript, e eles são interessantes:

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

// > first
// -> undefined

💡 Explicação:

A sentença rotulada é utilizada com os comandos break ou continue. Você pode utilizar um rótulo para identificar um laço de repetição, e então usar os comandos break ou continue para indicar quando um programa deverá interromper ou continuar a execução de um loop.

No exemplo acima, nós identificamos o rótulo foo. Depois disso, é executado o that console.log('first'); e depois interrompemos a execução.

Leia mais sobre rótulos no JavaScript:

Rótulos aninhados

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

💡 Explicação:

Parecido com os exemplos anteriores, acesse esses links:

try..catch traidor

O que a seguinte expressão irá retornar? 2 ou 3?

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

A resposta é 3. Surpreso?

💡 Explicação:

Isto é herança múltipla?

Observe o exemplo abaixo:

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

Isto é uma herança múltipla? Não.

💡 Explicação:

A parte interessante é o valor da cláusula extends ((String, Array)). O operador de agrupamento sempre retorna seu último argmento, então (String, Array) é somente Array. Isso significa que nós criamos uma classe que extende um Array.

Um gerador que produz a si mesmo

Considere o exemplo de um gerador que produz a si mesmo:

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

Como você pode ver, o valor retornado é um objeto com seu value igual a f. Nesse caso, nós podemos fazer algo assim:

(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 }

// e assim por diante
// ...

💡 Explicação:

Para entender porque isso funciona assim, leia essas seções da especificação:

Uma classe de classe

Considere essa sintaxe ofuscada:

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

Parece que estamos declarando uma classe dentro de outra. Isso deveria ser um erro, contudo, nós obtemos uma string com 'object'.

💡 Explicação:

Desde o ECMAScript 5, palavras reservadas são permitidas como nomes de propriedades. Pense nisso como esse exemplo simples de objeto:

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

O ES6 padronizou o atalho para definição de métodos. Também, classes podem ser anônimas. Então, se removermos a parte : function, teremos o seguinte resultado:

class {
  class() {}
}

O resultado de uma classe padrão será sempre um objeto simples. E o seu tipo deverá ser 'object'.

Leia mais aqui:

Objetos não coercíveis

Com símbolos bem conhecidos, aqui está uma maneira de se livrar da coerção de tipo. Dê uma olhada:

function nonCoercible(val) {
  if (val == null) {
    throw TypeError("nonCoercible should not be called with null or undefined");
  }

  const res = Object(val);

  res[Symbol.toPrimitive] = () => {
    throw TypeError("Trying to coerce non-coercible object");
  };

  return res;
}

Agora podemos usar isso dessa forma:

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

foo * 10; // -> TypeError: Trying to coerce non-coercible object
foo + "evil"; // -> TypeError: Trying to coerce non-coercible object

// strings
const bar = nonCoercible("bar");

bar + "1"; // -> TypeError: Trying to coerce non-coercible object
bar.toString() + 1; // -> bar1
bar === "bar"; // -> false
bar.toString() === "bar"; // -> true
bar == "bar"; // -> TypeError: Trying to coerce non-coercible object

// números
const baz = nonCoercible(1);

baz == 1; // -> TypeError: Trying to coerce non-coercible object
baz === 1; // -> false
baz.valueOf() === 1; // -> true

💡 Explicação:

Arrow functions traiçoeiras

Considere o exemplo abaixo:

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

Tá bom, legal, mas e agora isso:

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

💡 Explicação:

Você provavelmente espera {} ao invés de undefined. Isso se dá porque os colchetes ({}) são parte da sintaxe das arrow functions, então f retornará indefinido. Contudo é possível retornar o objeto {} diretamente da arrow function, fechando seu valor dentro das chaves (parênteses).

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

Arrow functions não podem ser construtores

Considere o exemplo abaixo:

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

Agora, tente fazer o mesmo com uma arrow function:

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

💡 Explicação:

Arrow functions não podem ser utilizadas como construtores e irão devolver um erro quando utilizadas com new. Porque elas possuem seu this léxico, e elas não possuem uma propriedade prototype, então isso não faria sentido.

arguments e arrow functions

Considere o exemplo abaixo:

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

Agora tente o mesmo com uma arrow function:

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

💡 Explicação:

Arrow functions são uma versão mais leve das funções regulares, com um foco em serem curtas e com o this léxico. Ao mesmo, arrow functions não fornecem uma ligacão para o objeto argumentos. Como uma alternativa, utilize os rest parameters para ter o mesmo resultado:

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

Retorno traiçoeiro

A sentença return também é traiçoeira. Considere o seguinte:

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

💡 Explicação:

return e a expressão retornada precisam estar na mesma linha:

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

Isso se dá por causa do conceito chamado Automatic Semicolon Insertion (Inserção Automática de Ponto e vírgula), que magicamente insere o ponto e vírgula (;) após a maioria das novas linhas. No primeiro exemplo, existe um ponto e vírgula entre a sentença return e o objeto, então a função retorna undefined e o objeto nunca é avaliado.

Encadeamento atribuições em um objeto

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

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

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

Da direita para a esquerda, {n: 2} é atribuído para foo, e o resultado dessa atribuição {n: 2} é atribuído para foo.x, e por isso bar é {n: 1, x: {n: 2}}, pois bar é uma referência a foo. Mas por que foo.x é indefinido enquanto bar.x não?

💡 Explicação:

Foo e bar referenciam o mesmo objeto {n: 1}, e l-values são resolvidos antes das atribuições. foo = {n: 2} está criando um novo objeto, e então foo é atualizado para referenciar esse novo objeto. O truque aqui é que foo em foo.x = ... como um l-value foi resolvido antes e continua referenciando o objeto antigo foo = {n: 1} e o atualiza adicionando o valor de x. Depois desse encadeamento, bar continua referenciando o objeto antigo foo, mas foo referencia o novo objeto {n: 2}, onde x não existe.

É equivalente a:

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

foo = {n: 2} // -> {n: 2}
bar.x = foo // -> {n: 1, x: {n: 2}}
// bar.x aponta para o novo objeto foo
// e não é equivalente a: bar.x = {n: 2}

Acessando propriedades de objetos usando arrays

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

obj[array]; // -> 1

E quanto aos arrays pseudo-multidimensionais>

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

💡 Explicação:

O operator de colchete [] converte a expressão passada usando toString. A conversão de um array de um elemento em uma string é semelhante à conversão de um elemento contido em uma string:

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

Null e Operadores Relacionais

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

null >= 0; // true

💡 Explicação:

Em resumo, se null é menos que 0 e false, então null >= 0 é true. Leia a explicação mais detalhada disso aqui.

Number.toFixed() mostra números diferentes

Number.toFixed() pode ter um comportamento estranho em navegadores diferentes. Veja esse exemplo:

(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

💡 Explicação:

Enquanto seu primeiro instinto é achar que o IE11 está correto e Firefox/Chrome estão errados, a realidade é que Firefox/Chrome são mais obedientes em padrões de números (IEEE-754 Floating Point), enquanto o IE11 é desobediente na tentativa de dar resultados mais claros.

Você pode ver isso acontecendo com alguns testes rápidos:

// Confirme o resultado ímpar do arredondamento de 5 para baixo 
(0.7875).toFixed(3); // -> 0.787
// Parece que é apenas 5 quando você expande para os
// limites da precisão de flutuação de 64 bits (precisão dupla)
(0.7875).toFixed(14); // -> 0.78750000000000
// Mas e se formos além do limite?
(0.7875).toFixed(20); // -> 0.78749999999999997780

Números de ponto flutuante não são salvos internamente como uma lista de dígitos decimais, mas com uma metodologia um pouco mais complicada que produz pequenas imprecisões que são usualmente arredondadas por toString ou chamadas similares, mas estão presentes internamente.

Nese caso, aquele "5" no final era atualmente uma fração extremamente pequena abaixo de um 5 verdadeiro. Arredondá-lo a qualquer comprimento razoável o tornará um 5... mas na verdade não é um 5 internamente.

O IE11, no entanto, relatará a entrada de valor apenas com zeros anexados ao final, mesmo no caso toFixed(20), pois parece estar arredondando à força o valor para reduzir os problemas dos limites de hardware.

Veja por referência NOTE 2 na definição do toFixed no ECMA-262.

Math.max() menor 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

💡 Explicação:

Comparando null com 0

As seguintes expressões parecem introduzir uma contradição:

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

Como null não pode ser igual nem maior que 0, se null> = 0 é realmente true? (Isso também funciona com menor que da mesma maneira.)

💡 Explicação:

O jeito que essas três expressões são avaliadas são diferentes e são responsáveis por produzirem esse comportamento inesperado.

Primeiro, a comparação abstrata de igualdade null == 0. Normalmente, se o operador não pode comparar os valores dos dois lados, ele converte ambos em números e compara os números. Então, você poderá esperar o seguinte comportamento:

// Isso não é o que acontece
(null == 0 + null) == +0;
0 == 0;
true;

Contudo, de acordo com a leitura da especificação, a conversão de números não pode acontecer em um lado que é null ou undefined. Portanto, se você tem null em um lado do sinal de igual, o outro lado precisa ser null ou undefined para que essa expressão retorne true. Como não é esse o caso, o retorno é false.

Depois, a comparação relacional null > 0. Aqui o algoritmo, diferentemente do operador abstrato de comparação, irá converter null em um número. Portanto, temos o seguinte comportamento:

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

Finalmente, a comparação relacional null >= 0. Você pode argumentar que essa expressão deveria ser o resultado de null > 0 || null == 0; se fosse esse o caso, então os resultados acima deveriam mostrar que isso também seria false. Todavia, o operador >= funciona de uma maneira diferente, onde basicamente ele se comporta de maneira oposta ao operador <. Como nosso exemplo acima com o operador maior que também é válido para o operador menor que, isso significa que essa expressão é realmente avaliada da seguinte forma:

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

Redeclaração da mesma variável

JS nos permite declarar variáveis das seguintes formas:

a;
a;
// This is also valid
a, a;

Funciona também no modo estrito:

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

💡 Explicação:

Todas as definições são combinadas em uma definição.

Comportamento padrão Array.prototype.sort()

Imagine que você precisa ordenar um array de números.

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

💡 Explicação:

A ordem padrão de ordenacão é feita na conversão dos elementos em texto, e depois comparando suas sequências de valores de unidades de código em UFT-16.

Dica

Passe comparefn se você tentar ordenar algo que não seja string.

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

📚 Outros recursos

  • wtfjs.com — uma coleção dessas várias irregularidades especiais, inconsistências e momentos dolorosos para cada linguagem da web.
  • Wat — Uma excelente palestra de Gary Bernhardt no CodeMash 2012
  • What the... JavaScript? — uma talk de Kyle Simpson para o Forward 2, que tenta “pull out the crazy” do JavaScript. Ele te ajuda a produzir código limpo, elegante, legível e inspirar a contribuir com a comunidade open source.

🎓 Licença

CC 4.0

© Denys Dovhan