Explorar E-books
Categorias
Explorar Audiolivros
Categorias
Explorar Revistas
Categorias
Explorar Documentos
Categorias
JavaScript
JavaScript é uma excelente linguagem. Ela tem uma sintaxe simples, um
ecossistema grande e, o mais importante, uma grande comunidade.
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.
Table of Contents
● Motivação
● Notação
● Exemplos
○ [] é igual a ![]
○ true não é igual a ![], nem igual a [] também
○ true é false
○ baNaNa
○ NaN não é um NaN
○ É uma falha
○ [] é verdadeiro, mas não true
○ null é falso, mas não false
○ document.all é um objeto (object), mas é indefinido (undefined)
○ Valor mínimo é maior que zero
○ function não é uma function
○ Somando arrays
○ Vírgulas finais em arrays
○ Igualdade entre arrays é um monstro
○ undefined e Number
○ parseInt é um vilão
○ Matemática com true e false
○ Comentários HTML são válidos no JavaScript
○ NaN não é um número
○ [] e null são objetos
○ Aumentando números magicamente
○ Precisão de 0.1 + 0.2
○ Patching numbers
○ Comparação de três números
○ Matemática engraçada
○ Soma de RegExps
○ Strings não são instâncias String
○ Chamando funções com backticks
○ Call call call
○ Uma propriedade constructor
○ Objeto como uma chave de uma propriedade de objeto
○ Acessando protótipos com __proto__
○ `${{Object}}`
○ Desestruturação com valores padrão
○ Pontos e dispersão
○ Rótulos
○ Rótulos aninhados
○ try..catch traidor
○ Isto é herança múltipla?
○ Um gerador que produz a si mesmo
○ Uma classe de classe
○ Objetos não coercíveis
○ Arrow functions traiçoeiras
○ Arrow functions não podem ser construtores
○ arguments e arrow functions
○ Retorno traiçoeiro
○ Encadeamento atribuições em um objeto
○ Acessando propriedades de objetos usando arrays
○ Null e Operadores Relacionais
○ Number.toFixed() mostra números diferentes
○ Math.max() menor que Math.min()
○ Comparando null com 0
○ Redeclaração da mesma variável
○ Comportamento padrão Array.prototype.sort()
Motivação
Just for fun
Notação
// -> é utilizado para mostrar o resultado de uma expressão. Por exemplo:
1 + 1; // -> 2
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;
Explicação:
true == []; // -> false
true == ![]; // -> false
toNumber(true); // -> 1
toNumber([]); // -> 0
1 == 0; // -> false
0 == 0; // -> 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).
Explicação:
A especificação define estritamente a lógica por trás desse comportamento:
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'
Explicação:
Aqui estão links das seções correspondentes especificação do ECMA-262:
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:
Esta é a parte da API Browser e não irá funcionar em um ambiente com Node.js
- apenas em navegadores
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.
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.
● 20.1.2.9 Number.MIN_VALUE
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");
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.
[[]] == 0 // true
[[]] == '' // 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:
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
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.
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.
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.
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
Por isso podemos adicionar valores booleanos (boolean) como números regulares e
obtermos os resultados corretos.
Seções correspondentes:
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:
Explicação:
Explicações de como os operadores typeof e instanceof funcionam:
// contudo
null instanceof Object; // false
Explicação:
O comportamento do operador typeof é definido nessa seção da especificação:
Object.prototype.toString.call(new Date());
// -> '[object Date]'
Object.prototype.toString.call(null);
// -> '[object Null]'
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:
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.
Patching numbers
Você pode adicionar seus próprios métodos em objetos como Number ou String.
Number.prototype.isOne = function() {
return Number(this) === 1;
};
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:
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
Nós podemos resolver isso com o operador Maior ou igual que (>=);
3 > 2 >= 1; // true
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'
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:
● 21.2.5.10 get RegExp.prototype.source
Explicação:
O construtor String retorna uma string:
typeof String("str"); // -> 'string'
String("str"); // -> 'str'
String("str") == "str"; // -> true
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.
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:
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'
● Object.prototype.constructor no MDN
● 19.1.3.1 Object.prototype.constructor
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 {}.
// estrutura:
// {
// '[object Object]': {
// '[object Object]': {}
// }
// }
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
● B.2.2.1 Object.prototype.proto
● 7.1.13 ToObject(argument)
`${{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]'.
Explicação:
let x,
{ x: y = 1 } = { x };
y;
// ↑ ↑ ↑ ↑
// 1 3 2 4
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.
Agora, detalhadamente:
[...'...'] // -> [ '.', '.', '.' ]
[...[...'...']] // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3
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.
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:
A resposta é 3. Surpreso?
Explicação:
● 13.15 The try Statement
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.
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 }
Explicação:
Para entender porque isso funciona assim, leia essas seções da especificação:
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'.
res[Symbol.toPrimitive] = () => {
throw TypeError("Trying to coerce non-coercible object");
};
return res;
}
// strings
const bar = nonCoercible("bar");
// números
const baz = nonCoercible(1);
Explicação:
● Um gist de Sergey Rubanov
● 6.1.5.1 Well-Known Symbols
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 }
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.
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");
Explicação:
return e a expressão retornada precisam estar na mesma linha:
(function() {
return {
b: 10
};
})(); // -> { b: 10 }
foo.x = foo = { n: 2 };
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;
obj[array]; // -> 1
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'
Explicação:
Em resumo, se null é menos que 0 e false, então null >= 0 é true. Leia a
explicação mais detalhada disso aqui.
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.
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.
Explicação:
● Por que Math.max() é menor que Math.min()? by Charlie Harvey
Como null não pode ser igual nem maior que0, 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.
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;
Explicação:
Todas as definições são combinadas em uma definição.
● 13.3.2 Variable Statement
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 ]