Skip to content

Latest commit

 

History

History
1786 lines (1242 loc) · 57.3 KB

README-it-it.md

File metadata and controls

1786 lines (1242 loc) · 57.3 KB

What the f*ck JavaScript?

WTFPL 2.0 NPM version

Una raccolta di snippet ingannevoli e divertenti scritti in JavaScript

JavaScript è un ottimo linguaggio. Ha una sintassi semplice, un grande ecosistema e, quello che conta veramente, una community fantastica.

Allo stesso tempo, sappiamo che JavaScript è un linguaggio abbastanza strano con delle parti cervellotiche. Alcune di queste possono rendere il nostro lavoro un inferno, alcune invece possono farci ridere a crepapelle.

L'idea per WTFJS è di Brian Leroux. Questo elenco è largamente ispirato al suo talk “WTFJS” at dotJS 2012:

dotJS 2012 - Brian Leroux - WTFJS

Node Packaged Manuscript

Puoi installare questo manuale con npm. Lancia semplicemente:

$ npm install -g wtfjs

Ora dovresti essere in grado di eseguire wtfjs dalla riga di comando. Altrimenti puoi continuare tranquillamente a leggerlo qui.

Il codice sorgente lo puoi trovare qui: https://github.com/denysdovhan/wtfjs

Traduzioni

Attualmente wtfjs è disponibile nelle seguenti lingue:

Richiedi un'altra traduzione

Table of Contents

💪🏻 Motivazione

Just for fun

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

Lo scopo principale di questo elenco è quello di raccogliere alcuni esempi strambi e mostrarne il loro funzionamento, se possibile. Semplicemente per il fatto che è divertente imparare qualcosa che non sapevamo prima.

Se sei un principiante puoi utilizzare questi appunti per approfondire JavaScript. Spero che questi appunti ti motivino a leggerne le specifiche.

Se sei uno sviluppatore senior, considera questi esempi come un'ottimo punto di riferimento per tutte quelle stranezze e stramberie del tuo amato JavaScript.

Ad ogni modo, leggilo. Probabilmente imparerai qualcosa di nuovo.

✍🏻 Notazione

// -> viene utilizzato per indicare il risultato di un'espressione. Ad esempio:

1 + 1; // -> 2

// > significa il risultato di console.log o di un altro output. Ad esempio:

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

// è semplicemente un commento utilizzato per le spiegazioni. Esempio:

// Assegnare una funzione ad una costante
const foo = function() {};

👀 Esempi

[] è uguale a ![]

Array è uguale a not array:

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

💡 Spiegazione:

L'opratore di abstract equality converte entrambi gli operandi prima di confrontarli, e diventano entrambi 0 per ragioni differenti. Gli Array sono truthy, quindi sulla destra, l'opposto di un valore truthy è false, che viene quindi forzato a diventare uno 0. Sul lato sinistro però l'array vuoto viene forzato a diventare un numero senza prima essere convertito in un valore booleano, e gli array vuoti vengono forzati a 0 a prescindere che siano truthy.

Qui possiamo vedere come viene semplificata l'espressione:

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

Vedi anche [] è truthy, ma non true.

true è diverso da ![], ma anche diverso da []

Array è diverso da true, ma anche not Array è diverso da true; Array è uguale a false, ma anche not Array è uguale a false:

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

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

💡 Spiegazione:

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

// Secondo le specifiche

true == []; // -> false

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

1 == 0; // -> false

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

![]; // -> false

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

// Secondo le specifiche

false == []; // -> true

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

0 == 0; // -> true

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

![]; // -> false

false == false; // -> true

true è false

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

💡 Spiegazione:

Considera questo, step-by-step:

// true è 'truthy' e rappresentato dal valore 1 (number), 'true' in formato stringa è NaN.
true == "true"; // -> false
false == "false"; // -> false

// 'false' non è la stringa vuota, quindi è un valore truthy
!!"false"; // -> true
!!"true"; // -> true

baNaNa

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

Questo è un giochino old-school in JavaScript, rivisitato. L'originale è questo:

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

💡 Spiegazione:

L'espressione viene valutata come 'foo' + (+'bar'), che converte 'bar' in "not a number".

NaN non è NaN

NaN === NaN; // -> false

💡 Spiegazione:

Le specifiche definiscono rigorosamente la logica dietro a questo comportamento:

  1. Se Type(x) è diverso da Type(y), return false.
  2. Se Type(x) è Number, allora
    1. Se x è NaN, return false.
    2. Se y è NaN, return false.
    3. … … …

7.2.14 Strict Equality Comparison

Seguendo la definizione di NaN da quella dell'IEEE:

Sono possibili quattro relazioni mutuamente esclusive: less than, equal, greater than, e unordered. L'ultimo caso si presenta quando almeno un operando è NaN. Tutt i NaN se comparati risulteranno unordered, inclusa la comparazione con se stesso.

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

È un fail

Non crederai ai tuoi occhi, ma...

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

💡 Spiegazione:

Rompendo quell'ammasso di simboli in pezzettini, possiamo notare che il seguente pattern si ripete spesso:

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

Quindi proviamo a sommare [] a false. Ma a causa di una serie di chiamate interne (binary + Operator -> ToPrimitive -> [[DefaultValue]]) otteniamo la conversione dell'operando a destra in una stringa:

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

Se pensiamo ad una stringa come un Array, possiamo accedere al suo primo elemento con [0]:

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

Il resto è ovvio, ma la i è complicata. La i in fail viene ottenuta generando la stringa 'falseundefined' e prendendo l'elemento all'indice ['10']

[] è truthy, ma non true

Un array è un valore truthy, ma non è uguale a true.

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

💡 Spiegazione:

Ecco i link alle sezioni corrispondenti della specifica ECMA-262:

null è falsy, ma non false

Nonostante il fatto che null sia un valore falsy, non è uguale a false.

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

Allo stesso modo, altri valori falsy, come 0 o '' sono uguali a false.

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

💡 Spiegazione:

La spiegazione è la stessa dell'esempio precedente. Ecco il link corrispondente:

document.all è un object, ma è undefined

⚠️ Questo fa parte delle Browser API e non funziona su Node.js ⚠️

Nonostante il fatto che document.all sia un oggetto array-like e permette l'accesso al DOM della pagina, risponde alla funzione typeof con undefined.

document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'

Allo stesso modo, document.all è diverso da undefined.

document.all === undefined; // -> false
document.all === null; // -> false

Ma contemporaneamente:

document.all == null; // -> true

💡 Spiegazione:

document.all veniva utilizzato per accedere agli elementi del DOM, nelle vecchie versioni di IE. Nonostante non sia mai diventato uno standard, veniva ampiamente utilizzato in codice JS non proprio recentissimo. Quando vennero rilasciate le nuove APIs (come document.getElementById) questa API divenne obsoleta e il comitato dello standard dovette decidere cosa farne. A causa del suo uso spropositato l'API venne mantenuta ma venne introdotta una violazione intenzionale nelle speficiche di JavaScript. Il motivo per il quale risponde a false quando si utilizza l'operatore di Strict Equality Comparison con undefined, mentre true quando si utilizza l'operatore di Abstract Equality Comparison è a causa della violazione intenzionale inserita nella specifica che la permette in modo esplicito.

“Obsolete features - document.all” at WhatWG - HTML spec — “Chapter 4 - ToBoolean - Falsy values” at YDKJS - Types & Grammar

Il numero più piccolo rappresentabile è maggiore di zero

Number.MIN_VALUE è il numero più piccolo rappresentabile, che è maggiore di zero:

Number.MIN_VALUE > 0; // -> true

💡 Spiegazione:

Number.MIN_VALUE è 5e-324, ovvero il più piccolo numero positivo che può essere rappresentato con precisione float, cioè quello che si può ottenere il più vicino possibile allo zero. Definisce la migliore risoluzione che un tipo di dato float può fornire.

Il numero più piccolo in assoluto è Number.NEGATIVE_INFINITY nonostante non sia effettivamente un tipo numerico.

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

function non è una function

⚠️ Un bug presente in V8 v5.5 o inferiore (Node.js <=7) ⚠️

Tutti conoscerete la noiosa undefined is not a function, ma questa?

// Dichiara una classe che estende null
class Foo extends null {}
// -> [Function: Foo]

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

💡 Spiegazione:

Questo non è parte delle specifiche. È semplicemente un bug che ora è stato risolto, quindi non dovrebbero esserci problemi con questo in futuro.

Sommare array

E se provassimo a sommare due array?

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

💡 Spiegazione:

Viene svolta la concatenazione. il procedimento step-by-step è il seguente:

[1, 2, 3] +
  [4, 5, 6][
    // chiama toString()
    (1, 2, 3)
  ].toString() +
  [4, 5, 6].toString();
// concatenazione
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");

"Trailing commas" in un array

Creiamo un array con 4 elementi vuoti. Nonostante ciò, si ottiene un array con 3 elementi, a causa delle "trailing commas":

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

💡 Spiegazione:

Trailing commas (anche chiamate "final commas") sono utili quando si aggiungono nuovi elementi, parametri o proprietà in codice JavaScript. Se si vuole aggiungere una nuova proprietà si può semplicemente aggiungere una nuova riga senza modificare quella precedente, se quella linea presenta già una virgola alla fine. Questo rende i diffs dei sistemi di version-control più puliti e modificare il codice è leggermente meno problematico.

Trailing commas at MDN

L'operatore di uguaglianza sugli array è un mostro

L'operatore di uguaglianza sugli array in JS è un mostro, come possiamo osservare sotto:

[] == ''   // -> 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

💡 Spiegazione:

Guarda attentamente gli esempi precedenti! Il comportamento viene spiegato nella sezione 7.2.13 Abstract Equality Comparison delle specifiche.

undefined e Number

Se non passiamo argomenti al costruttore di Number, otteniamo 0. Il valore undefined viene assegnato di default quando non viene passato alcun valore, quindi possiamo aspettarci che Number senza parametri prenda undefined come valore del suo parametro. Invece quando inseriamo undefined, otteniamo NaN.

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

💡 Spiegazione:

In base alle specifiche:

  1. Se non viene passato alcun parametro durante l'invocazione della funzione, n viene valorizzato a +0.
  2. Altrimenti, n sarà il risultato di ToNumber(value).
  3. Nel caso di undefined, ToNumber(undefined) deve restituire NaN.

Qui la sezione corrispondente:

parseInt è bast**do

parseInt è famoso per le sue stranezze:

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

💡 Spiegazione: Questo avviene perchè parseInt continuerà a svolgere il parsing carattere per carattere fino a che non trova un carattere che non riconosce. La f in 'f*ck' è la rappresentazione esadecimale di 15.

Svolgere il parsing di Infinity a integer è qualcosa di...

//
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

Attenzione anche quando si svolge il parsing di null:

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

💡 Spiegazione:

Si sta convertendo null alla stringa "null" e provando poi a convertirla a sua volta. Per le radici da 0 a 23, non ci sono numerali per svolgere la conversione, quindi viene restituito NaN. A 24, "n", la 14-esima lettera, viene aggiunta al sistema di numerazione. A 31, "u", la 21-esima lettera, viene aggiunta e l'intera stringa può essere decodificata. A 37 non c'è più un valido insieme di numerazione che si può generare quindi viene restituito NaN.

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

Non dimentichiamoci del sistema di numerazione ottale:

parseInt("06"); // 6
parseInt("08"); // 8 se  è presente il supporto a ECMAScript 5
parseInt("08"); // 0 se assente il supporto a ECMAScript 5

💡 Spiegazione: Se la stringa in input inizia con "0", la radice è 8 (octal) o 10 (decimal). Quale radice viene scelta dipende dall'implementazione. ECMAScript 5 specifica l'utilizzo di 10 (decimal), Ma non è ancora supportata da tutti i browser. Per questo motivo è sempre meglio specificare una radice quando si utilizza parseInt.

parseInt converte sempre l'input in stringa:

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

Attenzione quando si svolge il parsin di valori in virgola mobile:

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

💡 Spiegazione: ParseInt prende una stringa come argomento e restituisce un intero in base alla radice specificata. ParseInt inoltre elimina tutto ciò che viene dopo e incluso il primo carattere non numerico nella stringa passata come parametro. 0.000001 Viene convertito nella stringa "0.000001" e parseInt restituisce 0. Quando 0.0000001 viene convertito in stringa viene interpretato come "1e-7" e quindi parseInt restituisce 1. 1/1999999 viene interpretato come 5.00000250000125e-7 e parseInt restituisce 5.

Math con true e false

Facciamo un po' di calcoli:

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

Hmmm... 🤔

💡 Spiegazione:

Possiamo forzare dei valori a numeri utilizzando il costruttore di Number. È abbastanza ovvio che true venga forzato a 1:

Number(true); // -> 1

L'operatore unario + prova a convertire il suo valore in un numero. Può convertire la rappresentazione testuale di interie e float, così come i valori non testuali true, false, e null. Se non riesce a svolgere il parsing di un particolare valore, restuituirà NaN. Questo significa che possiamo forzare facilmente true a 1:

+true; // -> 1

Quando svolgiamo addizioni o moltiplicazioni, viene invocato il metodo ToNumber. In base alla specifica questo metodo restituisce:

Se parametro è true, restituisci 1. Se parametro è false, restituisci +0.

È questo il motivo per il quale possiamo sommare valori booleani e ottenere risultati corretti.

Sezioni corrispondenti:

I commenti HTML sono validi anche in JavaScript

Non ci crederai, ma <!-- (ovvero un commento in HTML) è un commento valido in JavaScript.

// commento valido
<!-- anche questo

💡 Spiegazione:

Stupito? Commenti HTML-like sono stati pensati per permettere ai browser che non capivano il tag <script> di degradare in modo soft. Questi browser, ad esempio Netscape 1.x non sono più diffusi. Quindi non c'è proprio più alcun motivo per inserire commenti HTML nei tag script

Dato che Node.js è basato sull'engine V8, i commenti HTML-like sono supportati anche dal runtime di the Node.js. Inoltre sono parte delle specifiche:

NaN è not a number

Il tipo di NaN è 'number':

typeof NaN; // -> 'number'

💡 Spiegazione:

Spiegazione di come funzionano gli operatori typeof e instanceof:

[] e null sono objects

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

// però
null instanceof Object; // false

💡 Spiegazione:

Il comportamento dell'operatore typeof è definito nella seguente sezione delle specifiche:

Secondo le specifiche, l'operatore typeof restituisce una stringa in base alla Table 35: typeof Operator Results. Per null, gli oggetti ordinari, esotici standard e non standard che non implementano [[Call]], restituisce la stringa "object".

Comunque si può anche controllare il tipo di un oggetto utilizzando il metodo toString.

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

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

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

Incrementare numeri magicamente

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

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

💡 Spiegazione:

Questo è causato dallo standard IEEE 754-2008 per l'aritmetica binaria dei numeri in virgola mobile. A questa grandezze numeriche, arrotonda al numero pari più vicino. Leggi di più qui:

La precisione di 0.1 + 0.2

Un giochino ben noto. La somma di 0.1 e 0.2 è completamente sbagliata:

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

💡 Spiegazione:

La risposta alla domanda ”La matematica in virgola mobile è completamente rotta? ” su StackOverflow:

Le costanti 0.2 e 0.3 nel programma saranno approssimazioni del loro vero valore. Il valore double più vicino a 0.2 è più grande del numero razionale 0.2 ma il double più vicino a 0.3 è più piccolo del numero razionale 0.3. La somma di 0.1 e 0.2 risulta essere più grande del numero razionale 0.3 e quindi risultando diverso dalla costante presente nel codice.

Questo problema è talmente noto che esiste anche il sito web 0.30000000000000004.com. Capita in tutti i linguaggi di programmazione che svolgono calcoli in virgola mobile, non solo JavaScript.

Patchare numeri

Possiamo aggiungere metodi nostri agli oggetti wrapper come Number o String.

Number.prototype.isOne = function() {
  return Number(this) === 1;
};

(1.0).isOne(); // -> true
(1).isOne(); // -> true
(2.0)
  .isOne()(
    // -> false
    7
  )
  .isOne(); // -> false

💡 Spiegazione:

Ovviamente possiamo estendere l'oggetto Number così come ogni altro oggetto in JavaScript. Non è comunque una pratica consigliata se il metodo definito non è parte delle specifiche. Ecco la lista delle proprietà dell'oggetto Number:

Confrontare tre numeri

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

💡 Spiegazione:

Perchè funziona in questo modo? Beh, il problema è nella prima parte dell'espressione. Ecco come funziona:

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

Possiamo correggerlo con l'operatore Greater than or equal (>=):

3 > 2 >= 1; // true

Leggi più a riguardo degli operatori relazionali nelle specifiche:

Matematica spassosa

Spesso il risultato delle operazioni aritmetiche in JavaScript risulta essere abbastanza strano. Consideriamo questi esempi:

 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

💡 Spiegazione:

Cosa succede nei primi quattro esempi? Ecco una piccola tabella per comprendere la somma in JavaScript:

Number  + Number  -> addition
Boolean + Number  -> addition
Boolean + Boolean -> addition
Number  + String  -> concatenation
String  + Boolean -> concatenation
String  + String  -> concatenation

E per quanto riguarda gli altri esempi? I metodi ToPrimitive e ToString vengono chiamati implicitamente per [] e {} prima della somma. Leggi di più riguardo a questo processo nelle specifiche:

In particolare l'eccezione è in {} + []. Il motivo per cui differisce da [] + {} è che, senza parentesi, viene interpretato come un blocco di codice seguito dall'operatore unario +, convertendo [] in un numero. Come viene spiegato di seguito:

{
  // qui un blocco di codice
}
+[]; // -> 0

Per ottenere lo stesso risultato di [] + {} possiamo racchiuderlo tra parentesi.

({} + []); // -> [object Object]

Somma di RegExps

Sapevi che si possono sommare numero in questo modo?

// Patch a toString method
RegExp.prototype.toString =
  function() {
    return this.source;
  } /
  7 /
  -/5/; // -> 2

💡 Spiegazione:

Le stringhe non sono istanze di String

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

💡 Spiegazione:

Il costruttore di String restituisce una stringa:

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

Proviamo con new:

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

Object? eh?

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

Più informazioni sul costruttore di String nelle specifiche:

Richiamare funzioni con le backticks

Dichiariamo una funzione che logga tutti i parametri nella console:

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

Senza dubbio, saprai che possiamo richiamarla nel modo seguente:

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

Ma sapevi di poter chiamare qualsiasi funzione con le backticks?

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

💡 Spiegazione:

Beh, questa non è per niente magia se hai familiarità con i Tagged template literals. Nell'esempio precedente, la funzione f f è un tag per i template literal. I tag prima dei template literals permettono di svolgere il parsing dei template con una funzione. Il primo parametro di una "funzione tag" contiene un array di stringhe. I parametri restanti sono relativi alle espressioni. Ad esempio:

function template(strings, ...keys) {
  // fai qualcosa con strings and keys…
}

Questa è la magia dietro famosa libreria chiamata 💅 styled-components, molto popolare tra la community di React.

Link alle specifiche:

Call call call

Trovato da @cramforce

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

💡 Spiegazione:

Attenzione, ti può mettere in crisi il cervello! Prova ad eseguire questo codice a mente: stiamo applicando il metodo call usando il metodo apply. Leggi di più:

Una proprietà chiamata constructor

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

💡 Spiegazione:

Consideriamo questo esempio passo passo:

// Dichiariamo una costante che è la stringa 'constructor'
const c = "constructor";

// c è una stringa
c; // -> 'constructor'

// Otteniamo il costruttore di string
c[c]; // -> [Function: String]

// Otteniamo il costruttore di constructor
c[c][c]; // -> [Function: Function]

// chiamiamo la funzione costruttore e gli passiamo
// il corpo di una nuova funzione come parametro
c[c][c]('console.log("WTF?")'); // -> [Function: anonymous]

// Chiamiamo la funzione anonima risultante
// Il risultato è loggare sulla console la stringa 'WTF?'
c[c][c]('console.log("WTF?")')(); // > WTF?

Object.prototype.constructor restituisce un riferimento al costruttore di Object che ha creato l'oggetto. Con le stringhe è String, nel caso dei numeri è Number e così via.

Un Object usato come key nelle property di un oggetto

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

💡 Spiegazione:

Perchè funziona così? Qui stiamo utilizzando le Computed property name. Quando passiamo un oggetto tra parentesi quadre, forza la conversione di quell'oggetto a stringa, quindi otteniamo la proprietà '[object Object]' e il valore {}.

Possiamo realizzare un "brackets hell" in questo modo:

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

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

Leggi di più a riguardo degli object literals qui:

Accedere ai prototypes con __proto__

Come sappiamo, i tipi primitivi non hanno prototipi. Però, se proviamo ad ottenere il valore di __proto__ per i tipi primitivi, otteniamo questo:

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

💡 Spiegazione:

Questo accade perchè quando qualcosa non ha un prototype, verrà inserito in un oggetto wrapper con un metodo ToObject. Quindi, passo passo:

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

Qui più informazioni riguardo a __proto__:

`${{Object}}`

Quale è il risultato dell'espressione qui sotto?

`${{ Object }}`;

La risposta è:

// -> '[object Object]'

💡 Spiegazione:

Abbiamo definito un oggetto con una proprietà Object usando la Shorthand property notation:

{
  Object: Object;
}

Quindi abbiamo passato questo oggetto al template literal, seguirà la chiamata al metodo toString per quell'oggetto. Ecco perchè otteniamo la stringa '[object Object]'.

Destructuring con valori di default

Considera l'esempio seguente:

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

L'esempio precedente è un ottima domanda per un colloquio di lavoro. Quale è il valore di y? La risposta è:

// -> 1

💡 Spiegazione:

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

Con l'esempio precedente:

  1. Dichiariamo x senza alcun valore, quindi risulta undefined.
  2. Quindi inseriamo il valore di x all'interno della proprietà x dell'oggetto.
  3. Quindi estraiamo il valore di x usando il destructuring e lo assegniamo a y. Se il valore non è definito, allora utilizziamo 1 come valore di default.
  4. Restituiamo il valore di y.

Puntini e lo spreading

Si possono realizzare esempi interessanti utilizzando l'operatore di spreading e gli array. Considera questo:

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

💡 Spiegazione:

Perchè 3? Quando utilizziamo l'operatore di spread, viene chiamato il metodo @@iterator, e l'iteratore che viene restituito viene utilizzato per ottenere i valori sui quali iterare. L'iteratore di default per le stringhe separa la stringa in caratteri. Dopo lo spreading, vengono inseriti questi valori in un array. Quindi viene svolto nuovamente lo spread sull'array e il risultato viene nuovamente inserito al suo interno.

La stringa '...' è composta da tre caratteri ., quindi la dimensione dell'array risultante è 3.

Ora, passo passo:

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

Chiaramente, possiamo svolgere questo procedimento di spread e wrap quante volte vogliamo:

[...'...']                 // -> [ '.', '.', '.' ]
[...[...'...']]            // -> [ '.', '.', '.' ]
[...[...[...'...']]]       // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]]  // -> [ '.', '.', '.' ]
// and so on …

Labels

Sono in pochi i programmatori che sono a conoscenza delle Labels in JavaScript. Sono abbastanza interessanti:

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

// > first
// -> undefined

💡 Spiegazione:

L'istruzione etichettata viene utilizzata con le istruzioni di break o continue. Possiamo usare un'etichetta per identificare costrutto iterativo, e usare le istruzioni break o continue per indicare se il programma deve interrompere l'iterazione o continuarla.

Nell'esempio precedente, identifichiamo l'etichetta foo. Dopo che console.log('first'); viene eseguita l'esecuzione viene fermata.

Approfondisci le etichette in JavaScript:

Labels annidate

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

💡 Spiegazione:

Simile agli esempi precedenti, clicca sui seguenti link:

Un try..catch insidioso

Cosa restituisce questa espressione? 2 o 3?

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

La risposta è 3. Sorpreso?

💡 Spiegazione:

Si tratta di ereditarietà multipla?

Dai uno sguardo all'esempio sottostante:

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

Si tratta di ereditarietà multipla? Negativo.

💡 Spiegazione:

La parte interessante è il valore della clausola ((String, Array)) di extends. L'operatore di grouping restituisce sempre il suo ultimo parametro, quindi (String, Array) è semplicemente Array. Questo significa che abbiamo creato una classe che estende Array.

Un generator che produce se stesso

Guarda questo esempio di generator che produce se stesso:

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

Come possiamo notare, il valore restituito è un oggetto con value uguale a f. In quel caso, possiamo fare una cosa del genere:

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

// and così via
// …

💡 Spiegazione:

Per capirne il suo funzionamento, leggi queste sezioni delle specifiche:

Una classe di tipo class

Considera questa sintassi offuscata in gioco:

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

Sembra la dichiarazione di una classe all'interno di un'altra classe. Dovrebbe essere un errore, invece otteniamo 'object'.

💡 Spiegazione:

Da ECMAScript 5, possiamo usare le keywords come property names. Quindi immaginalo come nel seguente esempio:

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

ES6 ha standardizzato la definizione compatta per i metodi. Inoltre, le classi possono essere anonime. Quindi se togliamo la parte con : function, otteniamo:

class {
  class() {}
}

Il risultato di una default class è sempre un oggetto semplice. E il tuo typeof dovrebbe restituire 'object'.

Leggi di più qui:

Oggetti non-coercible

Con i ben noti, esiste un modo per evitare la type-coercion. Guarda un po':

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

Adesso possiamo utilizzarla in questo modo:

// objects
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

// numbers
const baz = nonCoercible(1);

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

💡 Spiegazione:

Arrow functions strambe

Considera l'esempio sottostante:

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

Okay, va bene, ma guarda questo:

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

💡 Spiegazione:

Potresti aspettarti {} anzichè undefined. Questo è perchè le parentesi graffe fanno parte della sintassi per le arrow functions, quindi f restituirà undefined. È comunque possibile restituire l'oggetto {} direttamente da una arrow function, racchiudendo il valore di ritorno tra parentesi.

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

Arrow functions non possono essere un costruttore

Considera l'esempio sottostante:

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

Ora, prova a fare la stessa cosa con una arrow function:

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

💡 Spiegazione:

Le arrow function non possono essere utilizzate come costruttore e lanceranno un errore se usate con new. Dato che hanno un this lessicale, e non hanno la proprietà prototype, non avrebbe molto senso.

arguments e arrow functions

Considera l'esempio sottostante:

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

Ora, prova a fare la stessa cosa con una arrow function:

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

💡 Spiegazione:

Le arrow functions sono una versione alleggerita delle funzioni tradizionali con un focus sull'essere concise e con un this lessicale. Allo stesso tempo le arrow function non forniscono un binding per l'oggetto arguments. Un'alternativa valida per ottenere lo stesso risultato è utilizzare i rest parameters:

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

Uno strano return

anche l'istruzione return può essere complicata. Considera questo:

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

💡 Spiegazione:

return e l'espressione da restituire devono essere sulla stessa linea:

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

Questo a causa di un concetto chiamato Automatic Semicolon Insertion, che inserisce automagicamente punti e virgola dopo la maggior parte degli a capo. Nel primo esempio, c'è un punto e virgola inserito tra l'istruzione return e l'oggetto, quindi la funzione restituisce undefined e l'oggetto non viene mai valutato.

Concatenare assegnamenti su un object

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 destra a sinistra, {n: 2} viene assegnato a foo, e il risultato di questo assegnamento {n: 2} viene assegnato a foo.x, ecco perchè bar è {n: 1, x: {n: 2}} in quanto bar è un riferimento a foo. Ma perchè foo.x è undefined mentre bar.x non lo è?

💡 Spiegazione:

Foo e bar referenziano lo stesso oggetto {n: 1}, e gli lvalues vengono risolti prima dell'assegnamento. foo = {n: 2} sta creando un nuovo oggetto, quindi foo viene aggiornato per referenziare il nuovo oggetto. Il trick qui è in foo.x = ... in quanto il lvalue è stato risolto precedentemente e referenzia ancora il vecchio oggetto foo = {n: 1} e lo aggiorna inserendo il valore x. Dopo questa catena di assegnamenti, bar continua a referenziare il vecchio oggetto foo, ma foo referenzia il nuovo oggetto {n: 2}, dove x non esiste.

È equivalente a:

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

foo = { n: 2 }; // -> {n: 2}
bar.x = foo; // -> {n: 1, x: {n: 2}}
// bar.x point to the address of the new foo object
// it's not equivalent to: bar.x = {n: 2}

Accedere alle properties di un object con gli array

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

obj[array]; // -> 1

E per quanto concerne gli array pseudo-multidimensionali?

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

💡 Spiegazione:

L'operatore parentesi quadre [] converte l'espressione usando il metodo toString. Convertire un array di un solo elemento in una stringa è come convertire l'elemento contenuto nell'array in stringa.

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

Null e gli operatori relazionali

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

null >= 0; // true

💡 Spiegazione:

Per farla breve, se null che è minore di 0 è false, allora null >= 0 è true. Leggi la spiegazione approfondita per questo qui.

Number.toFixed() mostra numeri diversi

Number.toFixed() può comportarsi in modo bizzarro in certi browser. Guarda l'esempio seguente:

(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

💡 Spiegazione:

L'istinto potrebbe farci pensare che IE11 sia corretto e Firefox/Chrome sbaglino, la realtà è che Firefox/Chrome stanno rispettando gli standard per i numeri in virgola mobile (IEEE-754 Floating Point), mentre IE11 sta evitando di rispettarli (quello che probabilmente è) uno sforzo per restituire dei risultati più chiari.

Possiamo vedere perchè questo accade con un semplice test:

// Confermare lo strano risultato dell'arrotondamento per difetto di 5
(0.7875).toFixed(3); // -> 0.787
// Sembra essere 5 quando si estende il
// limite a 64-bit (double-precision) di precisione
(0.7875).toFixed(14); // -> 0.78750000000000
// Ma se si supera il limite?
(0.7875).toFixed(20); // -> 0.78749999999999997780

I numeri floating point non sono memorizzati come una sequenza di cifre decimali, ma attraverso un metodo più elaborato che produce delle piccole inacuratezze the solitamente vengono eliminate dalle chiamate a toString o simili, ma queste imprecisioni rimangono comunque presenti internamente.

In questo caso, il "5" alla fine era un numero infinitesimamente più piccolo del vero 5. Arrotondandolo ad una precisione ragionevole verrà mostrato come 5... ma internamente non è un 5.

IE11, invece, mostrerà il valore dato in input con degli zeri in coda, anche nel caso di toFixed(20), in quanto sembra forzare l'arrotondamento del valore per evitare problematiche causate dai limiti hardware.

Guarda il riferimento a NOTE 2 sulla definizione per toFixed nelle specifiche ECMA-262.

Math.max() più piccolo di 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

💡 Spiegazione:

Confrontare null con 0

La seguente espressione sembra introdurre una contraddizione:

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

Come può null non essere uguale a, o maggiore di 0, se null >= 0 è effettivamente true? (Funziona anche con "inferiore a" nello stesso modo.)

💡 Spiegazione:

Il modo in cui queste tre espressioni vengono valutate sono tutti diversi ed è per questo che viene prodotto questo comportamento un po' inaspettato.

Per prima cosa analizziamo il comportamento dell'operatore di abstract equality comparison, null == 0. Solitamente, se l'operatore non riesce a confrontare i suoi operanti in modo opportuno, li converte in numeri e compara questi ultimo. Quindi ci si può aspettare il seguente comportamento:

// This is not what happens
(null == 0 + null) == +0;
0 == 0;
true;

Invece, secondo una lettura attenta delle specifiche, la conversione a numero non avviene per l'operando che ha valore null o undefined. Quindi, se abbiamo null da un lato del simbolo uguale, l'altro lato deve essere null o undefined per fare in modo che venga restituito true. Dato che non è questo il caso, verrà restituito false.

Ora analizziamo l'operatore di comparazione null > 0. Qui l'algoritmo, a differenza dell'operatore di abstract equality, convertirà null in un numero. Quindi il comportamento sarà il seguente:

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

Infine, analizziamo l'operatore relazionale null >= 0. Si può obiettare che questa espressione dovrebbe essere il risultato di null > 0 || null == 0; se fosse così, allora il risultato dell'espressione dovrebbe essere false. Invece l'operatore >= funziona in un modo completamente diverso, dove praticamente prende l'opposto dell'operatore <. Dato che l'esempio con l'operatore "maggiore di" produce lo stesso valore dell'operatore "minore di", l'espressione verrà valutata nel modo seguente:

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

Alcune ridichiarazioni di variabili

JS permette la ridichiarazione di variabili:

a;
a;
// È valida anche questa
a, a;

Funziona anche in modalità strict:

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

💡 Spiegazione:

Tutte le definizione sono state unite in una sola.

Comportamento di default di Array.prototype.sort()

Supponiamo di voler ordinare un array di numeri.

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

💡 Spiegazione:

L'ordinamento di default viene realizzato convertendo gli elementi in stringhe, quindi confrontando i loro valore in UTF-16.

Suggerimento

Passa una comparefn se vuoi ordinare qualcosa che non è una stringa.

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

resolve() non restituisce un'istanza di Promise

const theObject = {
  a: 7
};
const thePromise = new Promise((resolve, reject) => {
  resolve(theObject);
}); // -> Instance object di Promise

thePromise.then(value => {
  console.log(value === theObject); // -> true
  console.log(value); // -> { a: 7 }
});

Il value che viene risolto da thePromise è esattamente theObject.

E se inserissimo un'altra Promise all'interno della funzione resolve?

const theObject = new Promise((resolve, reject) => {
  resolve(7);
}); // -> Promise instance object
const thePromise = new Promise((resolve, reject) => {
  resolve(theObject);
}); // -> Promise instance object

thePromise.then(value => {
  console.log(value === theObject); // -> false
  console.log(value); // -> 7
});

💡 Spiegazione:

Questa funzione appiattisce livelli annidati di oggetti promise-like (ad esempio una promise che risolve a una promise che risolve a qualcosa) in un singolo livello.

Promise.resolve() on MDN

La specifica è ECMAScript 25.6.1.3.2 Promise Resolve Functions. But it is not quite human-friendly.

📚 Other resources

  • wtfjs.com — una raccolta di irregolarità e stranezze davvero speciali con un pizzico di momenti dolorosamente controintuitivi per il linguaggio del web.
  • Wat — A lightning talk by Gary Bernhardt from CodeMash 2012
  • What the... JavaScript? — Il talk di Kyle Simpsons alla Forward 2 che prova a "estrarre le stramberie” da JavaScript. Il suo desiderio è aiutare a scrivere un codice più pulito, elegante e leggibile, ispirare le persone a contribuire alla community open source.

🎓 License

CC 4.0

© Denys Dovhan