JavaScript

Spiega la 'event delegation'

La 'event delegation' è una tecnica che consiste nell'aggiungere listener di eventi a un elemento genitore invece di aggiungerli agli elementi discendenti. Il listener si attiverà ogni volta che l'evento viene scatenato sugli elementi discendenti a causa del 'bubbling' dell'evento nel DOM. I vantaggi di questa tecnica sono:

  • L'ingombro della memoria diminuisce perché è necessario un solo handler sull'elemento genitore, anziché dover attaccare handler di eventi su ogni discendente.
  • Non è necessario 'unbinding' l'handler dagli elementi che vengono rimossi e 'binding' l'evento per i nuovi elementi.

Spiega come funziona 'this' in JavaScript

Non esiste una spiegazione semplice per 'this'; è uno dei concetti più confusi in JavaScript. Una spiegazione approssimativa è che il valore di 'this' dipende da come viene chiamata la funzione. Ho letto molte spiegazioni su 'this' online, e ho trovato la spiegazione di [Arnav Aggrawal] la più chiara. Si applicano le seguenti regole:

  1. Se la parola chiave 'new' viene utilizzata quando si chiama la funzione, 'this' all'interno della funzione è un oggetto nuovo di zecca.
  2. Se 'apply', 'call' o 'bind' vengono utilizzati per chiamare/creare una funzione, 'this' all'interno della funzione è l'oggetto che viene passato come argomento.
  3. Se una funzione viene chiamata come metodo, come 'obj.method()' — 'this' è l'oggetto di cui la funzione è una proprietà.
  4. Se una funzione viene richiamata come invocazione di funzione libera, ovvero è stata richiamata senza nessuna delle condizioni presenti sopra, 'this' è l'oggetto globale. In un browser, è l'oggetto 'window'. Se in modalità 'strict' ('use strict'), 'this' sarà 'undefined' invece dell'oggetto globale.
  5. Se si applicano più delle regole di cui sopra, vince la regola più in alto e imposterà il valore di 'this'.
  6. Se la funzione è una funzione freccia ES2015, ignora tutte le regole di cui sopra e riceve il valore 'this' del suo ambito circostante al momento della sua creazione.

Per una spiegazione approfondita, controlla il suo [articolo su Medium].

Puoi dare un esempio di uno dei modi in cui lavorare con 'this' è cambiato in ES6?

ES6 ti consente di utilizzare [funzioni freccia] che utilizzano l'[ambito lessicale racchiuso]. Questo è solitamente conveniente, ma impedisce al chiamante di controllare il contesto tramite '.call' o '.apply' — la conseguenza è che una libreria come 'jQuery' non legherà correttamente 'this' nelle tue funzioni di gestione degli eventi. Pertanto, è importante tenerlo presente quando si rifattorizzano grandi applicazioni legacy.

Spiega come funziona l'ereditarietà prototipale

Tutti gli oggetti JavaScript hanno una proprietà 'proto' con l'eccezione degli oggetti creati con 'Object.create(null)', che è un riferimento a un altro oggetto, che viene chiamato il 'prototipo' dell'oggetto. Quando si accede a una proprietà su un oggetto e se la proprietà non viene trovata su quell'oggetto, il motore JavaScript cerca nella 'proto' dell'oggetto, e nella 'proto' della 'proto' e così via, finché non trova la proprietà definita su una delle 'proto' o finché non raggiunge la fine della catena di prototipi. Questo comportamento simula l'ereditarietà classica, ma in realtà è più una [delegazione che ereditarietà].

Esempio di ereditarietà prototipale

// Costruttore dell'oggetto genitore. function Animal(name) { this.name = name; } // Aggiungi un metodo al prototipo dell'oggetto genitore. Animal.prototype.makeSound = function () { console.log('The ' + this.constructor.name + ' makes a sound.'); }; // Costruttore dell'oggetto figlio. function Dog(name) { Animal.call(this, name); // Chiama il costruttore genitore. } // Imposta il prototipo dell'oggetto figlio in modo che sia il prototipo del genitore. Object.setPrototypeOf(Dog.prototype, Animal.prototype); // Aggiungi un metodo al prototipo dell'oggetto figlio. Dog.prototype.bark = function () { console.log('Woof!'); }; // Crea una nuova istanza di Dog. const bolt = new Dog('Bolt'); // Chiama i metodi sull'oggetto figlio. console.log(bolt.name); // 'Bolt' bolt.makeSound(); // 'The Dog makes a sound.' bolt.bark(); // 'Woof!'

Da notare:

  • '.makeSound' non è definito su 'Dog', quindi il motore risale la catena di prototipi e trova '.makeSound' dall'ereditato 'Animal'.
  • L'uso di 'Object.create' per costruire la catena di ereditarietà non è più raccomandato. Usa 'Object.setPrototypeOf' invece.

Cosa ne pensi di AMD vs CommonJS?

Entrambi sono modi per implementare un sistema di moduli, che non era nativamente presente in JavaScript fino all'avvento di ES2015. CommonJS è sincrono mentre AMD (Asynchronous Module Definition) è ovviamente asincrono. CommonJS è progettato pensando allo sviluppo lato server, mentre AMD, con il suo supporto per il caricamento asincrono dei moduli, è più inteso per i browser.

Trovo la sintassi AMD piuttosto verbosa e CommonJS è più vicino allo stile in cui scriveresti le istruzioni di importazione in altri linguaggi. La maggior parte delle volte, trovo AMD inutile, perché se servissi tutto il tuo JavaScript in un unico file bundle concatenato, non trarresti beneficio dalle proprietà di caricamento asincrono. Inoltre, la sintassi CommonJS è più vicina allo stile di scrittura dei moduli di Node e c'è meno overhead di cambio di contesto quando si passa dallo sviluppo JavaScript lato client a quello lato server.

Sono contento che con i moduli ES2015, che supportano sia il caricamento sincrono che asincrono, possiamo finalmente attenerci a un unico approccio. Anche se non è stato completamente implementato nei browser e in Node, possiamo sempre usare i traspilatori per convertire il nostro codice.

Spiega perché il seguente codice non funziona come IIFE: 'function foo(){ }();'. Cosa deve essere cambiato per renderlo correttamente un IIFE?

IIFE sta per 'Immediately Invoked Function Expressions'. Il parser JavaScript legge 'function foo(){ }();' come 'function foo(){ }' e '();', dove il primo è una dichiarazione di funzione e il secondo (una coppia di parentesi) è un tentativo di chiamare una funzione ma non è stato specificato alcun nome, quindi genera 'Uncaught SyntaxError: Unexpected token )'.

Ecco due modi per correggerlo che prevedono l'aggiunta di altre parentesi: '(function foo(){ })()' e '(function foo(){ }())'. Le istruzioni che iniziano con 'function' sono considerate dichiarazioni di funzione; racchiudendo questa funzione all'interno di '()', diventa una espressione di funzione che può quindi essere eseguita con i successivi '()'. Queste funzioni non sono esposte nello scope globale e puoi persino ometterne il nome se non hai bisogno di fare riferimento a se stessa all'interno del corpo.

Potresti anche usare l'operatore 'void': 'void function foo(){ }();'. Sfortunatamente, c'è un problema con questo approccio. La valutazione dell'espressione data è sempre 'undefined', quindi se la tua funzione IIFE restituisce qualcosa, non puoi usarlo. Un esempio:

const foo = void (function bar() { return 'foo'; })(); console.log(foo); // undefined

Qual è la differenza tra una variabile che è: 'null', 'undefined' o non dichiarata? Come procederesti per controllare uno di questi stati?

Le variabili non dichiarate vengono create quando si assegna un valore a un identificatore che non è stato precedentemente creato utilizzando 'var', 'let' o 'const'. Le variabili non dichiarate verranno definite globalmente, al di fuori dell'ambito corrente. In modalità rigorosa, verrà generato un 'ReferenceError' quando si tenta di assegnare a una variabile non dichiarata. Le variabili non dichiarate sono cattive proprio come le variabili globali sono cattive. Evitale a tutti i costi! Per controllarle, racchiudi il loro utilizzo in un blocco 'try'/'catch'.

function foo() { x = 1; // Genera un ReferenceError in modalità rigorosa } foo(); console.log(x); // 1

Una variabile che è 'undefined' è una variabile che è stata dichiarata, ma non le è stato assegnato un valore. È di tipo 'undefined'. Se una funzione non restituisce alcun valore come risultato della sua esecuzione e viene assegnata a una variabile, anche la variabile ha il valore di 'undefined'. Per controllarla, confronta usando l'operatore di uguaglianza stretta ('===') o 'typeof' che darà la stringa 'undefined'. Nota che non dovresti usare l'operatore di uguaglianza astratta per il controllo, poiché restituirà 'true' anche se il valore è 'null'.

var foo; console.log(foo); // undefined console.log(foo === undefined); // true console.log(typeof foo === 'undefined'); // true console.log(foo == null); // true. Errato, non usarlo per controllare! function bar() {} var baz = bar(); console.log(baz); // undefined

Una variabile che è 'null' le sarà stato esplicitamente assegnato il valore 'null'. Rappresenta nessun valore ed è diversa da 'undefined' nel senso che è stata esplicitamente assegnata. Per controllare 'null', basta confrontare usando l'operatore di uguaglianza stretta. Nota che, come sopra, non dovresti usare l'operatore di uguaglianza astratta ('==') per il controllo, poiché restituirà 'true' anche se il valore è 'undefined'.

var foo = null; console.log(foo === null); // true console.log(typeof foo === 'object'); // true console.log(foo == undefined); // true. Errato, non usarlo per controllare!

Come abitudine personale, non lascio mai le mie variabili non dichiarate o non assegnate. Assegnerò esplicitamente 'null' a esse dopo la dichiarazione se non intendo usarle ancora. Se utilizzi un linter nel tuo flusso di lavoro, di solito sarà anche in grado di controllare che tu non faccia riferimento a variabili non dichiarate.

Cos'è una closure e come/perché la useresti?

Una closure è la combinazione di una funzione e dell'ambiente lessicale all'interno del quale tale funzione è stata dichiarata. La parola 'lessicale' si riferisce al fatto che lo scoping lessicale utilizza la posizione in cui una variabile è dichiarata all'interno del codice sorgente per determinare dove tale variabile è disponibile. Le closure sono funzioni che hanno accesso alle variabili della funzione esterna (racchiudente) - catena di scope anche dopo che la funzione esterna è tornata.

Perché la useresti?

  • Privacy dei dati / emulazione di metodi privati con closure. Comunemente usata nel [pattern del modulo].
  • [Applicazioni parziali o currying].

Puoi descrivere la differenza principale tra un ciclo '.forEach' e un ciclo '.map()' e perché sceglieresti l'uno piuttosto che l'altro?

Per capire le differenze tra i due, analizziamo cosa fa ogni funzione.

forEach

  • Itera gli elementi in un array.
  • Esegue un callback per ogni elemento.
  • Non restituisce un valore.
const a = [1, 2, 3]; const doubled = a.forEach((num, index) => { // Fai qualcosa con num e/o index. }); // doubled = undefined

map

  • Itera gli elementi in un array.
  • 'Mappa' ogni elemento a un nuovo elemento chiamando la funzione su ogni elemento, creando un nuovo array come risultato.
const a = [1, 2, 3]; const doubled = a.map((num) => { return num * 2; }); // doubled = [2, 4, 6]

La differenza principale tra '.forEach' e '.map()' è che '.map()' restituisce un nuovo array. Se hai bisogno del risultato, ma non desideri mutare l'array originale, '.map()' è la scelta migliore. Se hai semplicemente bisogno di iterare su un array, 'forEach' è una buona scelta.

Qual è un caso d'uso tipico per le funzioni anonime?

Possono essere utilizzate in IIFE per incapsulare del codice all'interno di uno scope locale in modo che le variabili dichiarate al suo interno non fuoriescano allo scope globale.

(function () { // Alcuni codici qui. })();

Come callback che viene usata una sola volta e non ha bisogno di essere usata altrove. Il codice sembrerà più autonomo e leggibile quando i gestori sono definiti direttamente all'interno del codice che li chiama, piuttosto che dover cercare altrove per trovare il corpo della funzione.

setTimeout(function () { console.log('Ciao mondo!'); }, 1000);

Argomenti per costrutti di programmazione funzionale o Lodash (simili ai callback).

const arr = [1, 2, 3]; const double = arr.map(function (el) { return el * 2; }); console.log(double); // [2, 4, 6]

Come organizzi il tuo codice? (pattern del modulo, ereditarietà classica?)

In passato, ho usato Backbone per i miei modelli, il che incoraggia un approccio più orientato agli oggetti, creando modelli Backbone e allegando metodi ad essi.

Il pattern del modulo è ancora ottimo, ma in questi giorni, uso React/Redux che utilizzano un flusso di dati unidirezionale basato sull'architettura Flux. Rappresenterei i modelli della mia app usando semplici oggetti e scriverei funzioni pure utility per manipolare questi oggetti. Lo stato viene manipolato usando azioni e reducer come in qualsiasi altra applicazione Redux.

Evito di usare l'ereditarietà classica quando possibile. Se e quando lo faccio, mi attengo a [queste regole].

Qual è la differenza tra 'host objects' e 'native objects'?

Gli oggetti nativi sono oggetti che fanno parte del linguaggio JavaScript definito dalla specifica ECMAScript, come 'String', 'Math', 'RegExp', 'Object', 'Function', ecc.

Gli oggetti host sono forniti dall'ambiente di runtime (browser o Node), come 'window', 'XMLHTTPRequest', ecc.

Differenza tra: 'function Person(){}', 'var person = Person()', e 'var person = new Person()'?

Questa domanda è piuttosto vaga. La mia migliore ipotesi sull'intenzione è che stia chiedendo informazioni sui costruttori in JavaScript. Tecnicamente parlando, 'function Person(){}' è solo una normale dichiarazione di funzione. La convenzione è usare PascalCase per le funzioni che sono destinate ad essere usate come costruttori.

'var person = Person()' invoca 'Person' come una funzione, e non come un costruttore. Invocare in questo modo è un errore comune se la funzione è destinata ad essere usata come costruttore. Tipicamente, il costruttore non restituisce nulla, quindi invocare il costruttore come una normale funzione restituirà 'undefined' e questo verrà assegnato alla variabile destinata come istanza.

'var person = new Person()' crea un'istanza dell'oggetto 'Person' usando l'operatore 'new', che eredita da 'Person.prototype'. Un'alternativa sarebbe usare 'Object.create', come: 'Object.create(Person.prototype)'.

function Person(name) { this.name = name; } var person = Person('John'); console.log(person); // undefined console.log(person.name); // Uncaught TypeError: Cannot read property 'name' of undefined var person = new Person('John'); console.log(person); // Person { name: 'John' } console.log(person.name); // 'john'

Qual è la differenza tra '.call' e '.apply'?

Sia '.call' che '.apply' vengono usati per invocare funzioni e il primo parametro verrà usato come valore di 'this' all'interno della funzione. Tuttavia, '.call' accetta argomenti separati da virgola come argomenti successivi, mentre '.apply' accetta un array di argomenti come argomento successivo. Un modo semplice per ricordarlo è C per 'call' e separati da virgola e A per 'apply' e un array di argomenti.

function add(a, b) { return a + b; } console.log(add.call(null, 1, 2)); // 3 console.log(add.apply(null, [1, 2])); // 3

Spiega 'Function.prototype.bind'.

Preso parola per parola da [MDN]:

Il metodo 'bind()' crea una nuova funzione che, quando chiamata, ha la sua parola chiave 'this' impostata sul valore fornito, con una data sequenza di argomenti che precedono qualsiasi argomento fornito quando la nuova funzione viene chiamata.

Nella mia esperienza, è più utile per legare il valore di 'this' nei metodi delle classi che si desidera passare ad altre funzioni. Questo viene frequentemente fatto nei componenti React.

Quando useresti 'document.write()'?

'document.write()' scrive una stringa di testo in un flusso di documenti aperto da 'document.open()'. Quando 'document.write()' viene eseguito dopo il caricamento della pagina, chiamerà 'document.open' che cancella l'intero documento ('<head>' e '<body>' rimossi!) e sostituisce i contenuti con il valore del parametro dato. Per questo è generalmente considerato pericoloso e soggetto a usi impropri.

Ci sono alcune risposte online che spiegano che 'document.write()' viene utilizzato nel codice di analisi o [quando si desidera includere stili che dovrebbero funzionare solo se JavaScript è abilitato]. Viene persino utilizzato in HTML5 boilerplate per [caricare script in parallelo e preservare l'ordine di esecuzione]! Tuttavia, sospetto che queste ragioni potrebbero essere obsolete e al giorno d'oggi possono essere raggiunte senza usare 'document.write()'. Per favore, correggimi se mi sbaglio su questo.

Qual è la differenza tra 'feature detection', 'feature inference' e l'uso della stringa UA?

Rilevamento delle funzionalità (Feature Detection)

Il rilevamento delle funzionalità consiste nel capire se un browser supporta un determinato blocco di codice e nell'eseguire codice diverso a seconda che lo supporti (o meno), in modo che il browser possa sempre fornire un'esperienza funzionante invece di crashare/generare errori in alcuni browser. Ad esempio:

if ('geolocation' in navigator) { // Può usare navigator.geolocation } else { // Gestisce la mancanza della funzionalità }

[Modernizr] è un'ottima libreria per gestire il rilevamento delle funzionalità.

Inferenza delle funzionalità (Feature Inference)

L'inferenza delle funzionalità controlla una funzionalità proprio come il rilevamento delle funzionalità, ma usa un'altra funzione perché presume che esisterà anche, ad esempio:

if (document.getElementsByTagName) { element = document.getElementById(id); }

Questo non è realmente raccomandato. Il rilevamento delle funzionalità è più a prova di errore.

Stringa UA (User Agent)

È una stringa riportata dal browser che consente ai peer del protocollo di rete di identificare il tipo di applicazione, il sistema operativo, il fornitore del software o la versione del software dell'agente utente del software richiedente. Può essere accessibile tramite 'navigator.userAgent'. Tuttavia, la stringa è difficile da analizzare e può essere falsificata. Ad esempio, Chrome si presenta sia come Chrome che come Safari. Quindi per rilevare Safari devi controllare la stringa Safari e l'assenza della stringa Chrome. Evita questo metodo.

Spiega Ajax nel maggior dettaglio possibile.

Ajax (JavaScript e XML asincroni) è un insieme di tecniche di sviluppo web che utilizzano molte tecnologie web lato client per creare applicazioni web asincrone. Con Ajax, le applicazioni web possono inviare e recuperare dati da un server in modo asincrono (in background) senza interferire con la visualizzazione e il comportamento della pagina esistente. Separando il livello di interscambio dei dati dal livello di presentazione, Ajax consente alle pagine web, e per estensione alle applicazioni web, di modificare il contenuto dinamicamente senza la necessità di ricaricare l'intera pagina. In pratica, le implementazioni moderne utilizzano comunemente JSON anziché XML, a causa dei vantaggi di JSON essere nativo di JavaScript.

L'API 'XMLHttpRequest' è frequentemente utilizzata per la comunicazione asincrona oggigiorno, l'API 'fetch()'.

Quali sono i vantaggi e gli svantaggi dell'utilizzo di Ajax?

Vantaggi

  • Migliore interattività. I nuovi contenuti dal server possono essere modificati dinamicamente senza la necessità di ricaricare l'intera pagina.
  • Riduce le connessioni al server poiché script e fogli di stile devono essere richiesti una sola volta.
  • Lo stato può essere mantenuto su una pagina. Le variabili JavaScript e lo stato del DOM persisteranno perché la pagina del contenitore principale non è stata ricaricata.
  • In pratica la maggior parte dei vantaggi di una SPA.

Svantaggi

  • Le pagine web dinamiche sono più difficili da aggiungere ai segnalibri.
  • Non funziona se JavaScript è stato disabilitato nel browser.
  • Alcuni crawler web non eseguono JavaScript e non vedrebbero il contenuto caricato da JavaScript.
  • Le pagine web che utilizzano Ajax per recuperare dati dovranno probabilmente combinare i dati remoti recuperati con i template lato client per aggiornare il DOM. Per far sì che ciò accada, JavaScript dovrà essere analizzato ed eseguito nel browser, e i dispositivi mobili di fascia bassa potrebbero avere difficoltà con questo.
  • In pratica la maggior parte degli svantaggi di una SPA.

Spiega come funziona JSONP (e come non è propriamente Ajax).

JSONP (JSON con Padding) è un metodo comunemente usato per aggirare le policy di 'cross-domain' nei browser web perché le richieste Ajax dalla pagina corrente a un dominio 'cross-origin' non sono consentite.

JSONP funziona effettuando una richiesta a un dominio 'cross-origin' tramite un tag '<script>' e di solito con un parametro di query 'callback', ad esempio: 'https://example.com?callback=printData'. Il server quindi avvolgerà i dati all'interno di una funzione chiamata 'printData' e li restituirà al client.

<script> function printData(data) { console.log(`Il mio nome è ${data.name}!`); } </script> <script src="[https://example.com?callback=printData](https://example.com?callback=printData)"></script>
printData({ name: 'Yang Shun' });

Il client deve avere la funzione 'printData' nel suo ambito globale e la funzione verrà eseguita dal client quando la risposta dal dominio 'cross-origin' viene ricevuta.

JSONP può essere insicuro e ha alcune implicazioni sulla sicurezza. Poiché JSONP è in realtà JavaScript, può fare tutto ciò che JavaScript può fare, quindi è necessario fidarsi del fornitore dei dati JSONP.

Al giorno d'oggi, [CORS] è l'approccio raccomandato e JSONP è visto come un hack.

Hai mai usato il templating JavaScript? Se sì, quali librerie hai usato?

Sì. Handlebars, Underscore, Lodash, AngularJS e JSX. Non mi piaceva il templating in AngularJS perché faceva un uso eccessivo di stringhe nelle direttive e gli errori di battitura non venivano rilevati. JSX è il mio nuovo preferito in quanto è più vicino a JavaScript e non c'è quasi nessuna sintassi da imparare. Oggi, puoi persino usare i 'template string literals' di ES2015 come un modo rapido per creare template senza dipendere da codice di terze parti.

const template = `<div>Il mio nome è: ${name}</div>`;

Tuttavia, fai attenzione a un potenziale XSS nell'approccio di cui sopra poiché i contenuti non vengono 'escaped' per te, a differenza delle librerie di templating.

Spiega 'hoisting'.

'Hoisting' è un termine usato per spiegare il comportamento delle dichiarazioni di variabili nel tuo codice. Le variabili dichiarate o inizializzate con la parola chiave 'var' avranno la loro dichiarazione 'spostata' verso l'alto nel loro ambito a livello di modulo/funzione, che chiamiamo 'hoisting'. Tuttavia, solo la dichiarazione viene 'hoisted', l'assegnazione (se presente) rimarrà dove si trova.

Si noti che la dichiarazione non viene effettivamente spostata: il motore JavaScript analizza le dichiarazioni durante la compilazione e diventa consapevole delle dichiarazioni e dei loro ambiti. È semplicemente più facile capire questo comportamento visualizzando le dichiarazioni come se fossero 'hoisted' nella parte superiore del loro ambito. Spieghiamo con alcuni esempi.

console.log(foo); // undefined var foo = 1; console.log(foo); // 1

Le dichiarazioni di funzione hanno il corpo 'hoisted' mentre le espressioni di funzione (scritte sotto forma di dichiarazioni di variabili) hanno solo la dichiarazione di variabile 'hoisted'.

// Dichiarazione di funzione console.log(foo); // [Function: foo] foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); } console.log(foo); // [Function: foo] // Espressione di funzione console.log(bar); // undefined bar(); // Uncaught TypeError: bar is not a function var bar = function () { console.log('BARRRR'); }; console.log(bar); // [Function: bar]

Anche le variabili dichiarate tramite 'let' e 'const' vengono 'hoisted'. Tuttavia, a differenza di 'var' e 'function', non vengono inizializzate e l'accesso a esse prima della dichiarazione comporterà un'eccezione 'ReferenceError'. La variabile si trova in una 'temporal dead zone' dall'inizio del blocco fino a quando la dichiarazione non viene elaborata.

x; // undefined y; // Reference error: y is not defined var x = 'local'; let y = 'local';

Descrivi il 'bubbling' degli eventi.

Quando un evento si attiva su un elemento DOM, tenterà di gestire l'evento se c'è un listener allegato, quindi l'evento si propaga al suo genitore e accade la stessa cosa. Questo 'bubbling' si verifica risalendo gli antenati dell'elemento fino al 'document'. Il 'bubbling' degli eventi è il meccanismo alla base della 'event delegation'.

Qual è la differenza tra un 'attribute' e una 'property'?

Gli 'attribute' sono definiti sul markup HTML, ma le 'property' sono definite sul DOM. Per illustrare la differenza, immagina di avere questo campo di testo nel nostro HTML: '<input type="text" value="Hello">'.

const input = document.querySelector('input'); console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello

Ma dopo aver cambiato il valore del campo di testo aggiungendovi "World!", questo diventa:

console.log(input.getAttribute('value')); // Hello console.log(input.value); // Hello World!

Perché estendere gli oggetti JavaScript incorporati non è una buona idea?

Estendere un oggetto JavaScript incorporato/nativo significa aggiungere proprietà/funzioni al suo 'prototype'. Sebbene all'inizio possa sembrare una buona idea, in pratica è pericoloso. Immagina che il tuo codice utilizzi alcune librerie che estendono entrambe l''Array.prototype' aggiungendo lo stesso metodo 'contains', le implementazioni si sovrascriveranno a vicenda e il tuo codice si romperà se il comportamento di questi due metodi non è lo stesso.

L'unico momento in cui potresti voler estendere un oggetto nativo è quando vuoi creare un polyfill, fornendo essenzialmente la tua implementazione per un metodo che fa parte della specifica JavaScript ma potrebbe non esistere nel browser dell'utente a causa del fatto che è un browser più vecchio.

Differenza tra l'evento 'load' del documento e l'evento 'DOMContentLoaded' del documento?

L'evento 'DOMContentLoaded' viene attivato quando il documento HTML iniziale è stato completamente caricato e analizzato, senza attendere che i fogli di stile, le immagini e i sottocornici abbiano terminato il caricamento.

L'evento 'load' della 'window' viene attivato solo dopo che il DOM e tutte le risorse e gli asset dipendenti sono stati caricati.

Qual è la differenza tra '==' e '==='?

'==' è l'operatore di uguaglianza astratta mentre '===' è l'operatore di uguaglianza stretta. L'operatore '==' confronterà per l'uguaglianza dopo aver eseguito le conversioni di tipo necessarie. L'operatore '===' non eseguirà la conversione di tipo, quindi se due valori non sono dello stesso tipo '===' restituirà semplicemente 'false'. Quando si usa '==', possono accadere cose strane, come:

1 == '1'; // true 1 == [1]; // true 1 == true; // true 0 == ''; // true 0 == '0'; // true 0 == false; // true

Il mio consiglio è di non usare mai l'operatore '==', tranne per comodità quando si confronta con 'null' o 'undefined', dove 'a == null' restituirà 'true' se 'a' è 'null' o 'undefined'.

var a = null; console.log(a == null); // true console.log(a == undefined); // true

Spiega la 'same-origin policy' per quanto riguarda JavaScript.

La 'same-origin policy' impedisce a JavaScript di effettuare richieste tra domini diversi. Un'origine è definita come una combinazione di schema URI, nome host e numero di porta. Questa policy impedisce a uno script dannoso su una pagina di ottenere l'accesso a dati sensibili su un'altra pagina web tramite il Document Object Model di quella pagina.

Fai funzionare questo:

duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]
function duplicate(arr) { return arr.concat(arr); } duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Oppure con ES6:

const duplicate = (arr) => [...arr, ...arr]; duplicate([1, 2, 3, 4, 5]); // [1,2,3,4,5,1,2,3,4,5]

Perché si chiama espressione ternaria, cosa indica la parola 'Ternary'?

'Ternary' indica tre, e un'espressione ternaria accetta tre operandi, la condizione di test, l'espressione 'then' e l'espressione 'else'. Le espressioni ternarie non sono specifiche di JavaScript e non sono sicuro del perché sia ​​incluso in questo elenco.

Cos'è '"use strict";'? Quali sono i vantaggi e gli svantaggi di usarlo?

'use strict' è un'istruzione usata per abilitare la modalità rigorosa a interi script o singole funzioni. La modalità rigorosa è un modo per scegliere una variante ristretta di JavaScript.

Vantaggi:

  • Rende impossibile creare accidentalmente variabili globali.
  • Fa sì che le assegnazioni che altrimenti fallirebbero silenziosamente generino un'eccezione.
  • Fa sì che i tentativi di eliminare proprietà non eliminabili generino un'eccezione (dove prima il tentativo non avrebbe semplicemente alcun effetto).
  • Richiede che i nomi dei parametri di funzione siano unici.
  • 'this' è indefinito nel contesto globale.
  • Cattura alcuni errori di codifica comuni, generando eccezioni.
  • Disabilita funzionalità confuse o mal concepite.

Svantaggi:

  • Molte funzionalità mancanti a cui alcuni sviluppatori potrebbero essere abituati.
  • Nessun accesso a 'function.caller' e 'function.arguments'.
  • La concatenazione di script scritti in diverse modalità rigorose potrebbe causare problemi.

Nel complesso, penso che i benefici superino gli svantaggi, e non ho mai dovuto fare affidamento sulle funzionalità che la modalità rigorosa blocca. Consiglierei di usare la modalità rigorosa.

Crea un ciclo 'for' che itera fino a '100' emettendo **'fizz'** ai multipli di '3', **'buzz'** ai multipli di '5' e **'fizzbuzz'** ai multipli di '3' e '5'.

Dai un'occhiata a questa versione di FizzBuzz di [Paul Irish].

for (let i = 1; i <= 100; i++) { let f = i % 3 == 0, b = i % 5 == 0; console.log(f ? (b ? 'FizzBuzz' : 'Fizz') : b ? 'Buzz' : i); }

Non ti consiglio di scrivere quanto sopra durante i colloqui. Attieniti all'approccio lungo ma chiaro. Per versioni più stravaganti di FizzBuzz, controlla il link di riferimento qui sotto.

Perché è generalmente una buona idea lasciare l'ambito globale di un sito web così com'è e non toccarlo mai?

Ogni script ha accesso all'ambito globale, e se tutti usano lo spazio dei nomi globale per definire le loro variabili, probabilmente si verificheranno collisioni. Usa il pattern del modulo (IIFE) per incapsulare le tue variabili all'interno di uno spazio dei nomi locale.

Perché useresti qualcosa come l'evento 'load'? Questo evento ha svantaggi? Conosci alternative, e perché le useresti?

L'evento 'load' si attiva alla fine del processo di caricamento del documento. A questo punto, tutti gli oggetti nel documento sono nel DOM, e tutte le immagini, gli script, i link e i sub-frame hanno terminato il caricamento.

L'evento DOM 'DOMContentLoaded' si attiverà dopo che il DOM per la pagina è stato costruito, ma non attende che le altre risorse abbiano terminato il caricamento. Questo è preferito in alcuni casi quando non è necessario che l'intera pagina sia caricata prima dell'inizializzazione.

Spiega cos'è una 'single page app' e come renderla 'SEO-friendly'.

Quanto segue è tratto dalla fantastica [Grab Front End Guide], che, per coincidenza, è stata scritta da me!

Gli sviluppatori web oggigiorno si riferiscono ai prodotti che creano come app web, piuttosto che siti web. Sebbene non ci sia una differenza rigorosa tra i due termini, le app web tendono ad essere altamente interattive e dinamiche, consentendo all'utente di eseguire azioni e ricevere una risposta alla propria azione. Tradizionalmente, il browser riceve l'HTML dal server e lo renderizza. Quando l'utente naviga verso un'altra URL, è necessario un aggiornamento completo della pagina e il server invia nuovo HTML fresco alla nuova pagina. Questo è chiamato rendering lato server.

Tuttavia, nelle moderne SPA, viene utilizzato invece il rendering lato client. Il browser carica la pagina iniziale dal server, insieme agli script (framework, librerie, codice dell'app) e ai fogli di stile necessari per l'intera app. Quando l'utente naviga verso altre pagine, un aggiornamento della pagina non viene attivato. L'URL della pagina viene aggiornato tramite l'[HTML5 History API]. I nuovi dati richiesti per la nuova pagina, solitamente in formato JSON, vengono recuperati dal browser tramite richieste [AJAX] al server. La SPA quindi aggiorna dinamicamente la pagina con i dati tramite JavaScript, che ha già scaricato nel caricamento iniziale della pagina. Questo modello è simile a come funzionano le app mobili native.

I vantaggi:

  • L'app risulta più reattiva e gli utenti non vedono il 'flash' tra le navigazioni delle pagine a causa degli aggiornamenti completi della pagina.
  • Vengono effettuate meno richieste HTTP al server, poiché gli stessi asset non devono essere scaricati di nuovo per ogni caricamento di pagina.
  • Chiara separazione delle preoccupazioni tra client e server; puoi facilmente costruire nuovi client per diverse piattaforme (es. mobile, chatbot, smart watch) senza dover modificare il codice del server. Puoi anche modificare lo stack tecnologico sul client e sul server in modo indipendente, purché il contratto API non venga violato.

Gli svantaggi:

  • Caricamento iniziale della pagina più pesante a causa del caricamento del framework, del codice dell'app e degli asset richiesti per più pagine.
  • C'è un passaggio aggiuntivo da fare sul tuo server che consiste nel configurarlo per instradare tutte le richieste a un singolo punto di ingresso e consentire al routing lato client di prendere il sopravvento da lì.
  • Le SPA si basano su JavaScript per renderizzare il contenuto, ma non tutti i motori di ricerca eseguono JavaScript durante la scansione, e potrebbero vedere contenuto vuoto sulla tua pagina. Questo danneggia inavvertitamente l'ottimizzazione per i motori di ricerca (SEO) della tua app. Tuttavia, la maggior parte delle volte, quando si costruiscono app, il SEO non è il fattore più importante, poiché non tutto il contenuto deve essere indicizzabile dai motori di ricerca. Per ovviare a questo, puoi sia renderizzare lato server la tua app sia usare servizi come [Prerender] per 'renderizzare il tuo javascript in un browser, salvare l'HTML statico e restituirlo ai crawler'.

Qual è la tua esperienza con le Promise e/o i loro polyfill?

Possiedo una conoscenza pratica. Una promise è un oggetto che può produrre un singolo valore in futuro: o un valore risolto o una ragione per cui non è risolto (ad esempio, si è verificato un errore di rete). Una promise può trovarsi in uno dei 3 stati possibili: fulfilled, rejected o pending. Gli utenti di promise possono allegare callback per gestire il valore fulfilled o la ragione del rejected.

Alcuni polyfill comuni sono '$.deferred', Q e Bluebird, ma non tutti sono conformi alla specifica. ES2015 supporta le Promise fin da subito e i polyfill tipicamente non sono più necessari al giorno d'oggi.

Quali sono i pro e i contro dell'uso delle Promise invece delle callback?

Pro

  • Evita il 'callback hell' che può essere illeggibile.
  • Rende facile scrivere codice asincrono sequenziale leggibile con '.then()'.
  • Rende facile scrivere codice asincrono parallelo con 'Promise.all()'.
  • Con le promise, questi scenari, presenti nella codifica solo con callback, non accadranno:
    • Chiamare la callback troppo presto
    • Chiamare la callback troppo tardi (o mai)
    • Chiamare la callback troppo poche o troppe volte
    • Non passare alcun ambiente/parametro necessario
    • Ingoiare eventuali errori/eccezioni che potrebbero accadere

Contro

  • Codice leggermente più complesso (discutibile).
  • Nei browser più vecchi dove ES2015 non è supportato, è necessario caricare un polyfill per poterlo utilizzare.

Quali sono alcuni dei vantaggi/svantaggi di scrivere codice JavaScript in un linguaggio che compila in JavaScript?

Alcuni esempi di linguaggi che compilano in JavaScript includono CoffeeScript, Elm, ClojureScript, PureScript e TypeScript.

Vantaggi:

  • Risolve alcuni dei problemi di lunga data in JavaScript e scoraggia gli anti-pattern di JavaScript.
  • Ti consente di scrivere codice più breve, fornendo un po' di 'syntactic sugar' in più rispetto a JavaScript, che penso manchi a ES5, ma ES2015 è eccezionale.
  • I tipi statici sono fantastici (nel caso di TypeScript) per progetti di grandi dimensioni che devono essere mantenuti nel tempo.

Svantaggi:

  • Richiede un processo di 'build'/compilazione poiché i browser eseguono solo JavaScript e il tuo codice dovrà essere compilato in JavaScript prima di essere servito ai browser.
  • Il debugging può essere un incubo se le tue 'source map' non si mappano bene alla tua sorgente pre-compilata.
  • La maggior parte degli sviluppatori non ha familiarità con questi linguaggi e dovrà impararli. C'è un costo di apprendimento per il tuo team se lo usi per i tuoi progetti.
  • Comunità più piccola (dipende dal linguaggio), il che significa che risorse, tutorial, librerie e strumenti sarebbero più difficili da trovare.
  • Il supporto IDE/editor potrebbe essere carente.
  • Questi linguaggi saranno sempre indietro rispetto all'ultimo standard JavaScript.
  • Gli sviluppatori dovrebbero essere consapevoli di ciò in cui il loro codice viene compilato - perché quello è ciò che effettivamente verrà eseguito, e quello è ciò che conta alla fine.

In pratica, ES2015 ha notevolmente migliorato JavaScript e lo ha reso molto più piacevole da scrivere. Al giorno d'oggi non vedo davvero la necessità di CoffeeScript.

Quali strumenti e tecniche usi per il debugging del codice JavaScript?

  • React e Redux
    • [React Devtools]
    • [Redux Devtools]
  • Vue
    • [Vue Devtools]
  • JavaScript
    • [Chrome Devtools]
    • istruzione 'debugger'
    • Il buon vecchio debugging con 'console.log'

Quali costrutti del linguaggio usi per iterare le proprietà degli oggetti e gli elementi degli array?

Per gli oggetti:

  • Cicli 'for-in' - 'for (var property in obj) { console.log(property); }'. Tuttavia, questo itererà anche le sue proprietà ereditate, e dovrai aggiungere un controllo 'obj.hasOwnProperty(property)' prima di usarlo.
  • 'Object.keys()' - 'Object.keys(obj).forEach(function (property) { ... })'. 'Object.keys()' è un metodo statico che elencherà tutte le proprietà enumerabili dell'oggetto che gli passi.
  • 'Object.getOwnPropertyNames()' - 'Object.getOwnPropertyNames(obj).forEach(function (property) { ... })'. 'Object.getOwnPropertyNames()' è un metodo statico che elencherà tutte le proprietà enumerabili e non enumerabili dell'oggetto che gli passi.

Per gli array:

  • Cicli 'for' - 'for (var i = 0; i < arr.length; i++)'. L'errore comune qui è che 'var' è nell'ambito della funzione e non nell'ambito del blocco e la maggior parte delle volte si desidera una variabile iteratore con ambito di blocco. ES2015 introduce 'let' che ha ambito di blocco ed è consigliato usarlo invece. Quindi questo diventa: 'for (let i = 0; i < arr.length; i++)'.
  • 'forEach' - 'arr.forEach(function (el, index) { ... })'. Questo costrutto può essere più conveniente a volte perché non devi usare l''index' se tutto ciò di cui hai bisogno sono gli elementi dell'array. Ci sono anche i metodi 'every' e 'some' che ti permetteranno di terminare l'iterazione in anticipo.
  • Cicli 'for-of' - 'for (let elem of arr) { ... }'. ES6 introduce un nuovo ciclo, il ciclo 'for-of', che ti consente di iterare su oggetti che si conformano al protocollo iterabile come 'String', 'Array', 'Map', 'Set', ecc. Combina i vantaggi del ciclo 'for' e del metodo 'forEach()'. Il vantaggio del ciclo 'for' è che puoi interromperlo, e il vantaggio di 'forEach()' è che è più conciso del ciclo 'for' perché non hai bisogno di una variabile contatore. Con il ciclo 'for-of', ottieni sia la capacità di interrompere un ciclo che una sintassi più concisa.

La maggior parte delle volte, preferirei il metodo '.forEach', ma dipende davvero da quello che stai cercando di fare. Prima di ES6, usavamo i cicli 'for' quando avevamo bisogno di terminare prematuramente il ciclo usando 'break'. Ma ora con ES6, possiamo farlo con i cicli 'for-of'. Userei i cicli 'for' quando ho bisogno di ancora più flessibilità, come incrementare l'iteratore più di una volta per ciclo.

Inoltre, quando si usa il ciclo 'for-of', se è necessario accedere sia all'indice che al valore di ogni elemento dell'array, è possibile farlo con il metodo 'entries()' dell'Array ES6 e la destrutturazione:

const arr = ['a', 'b', 'c']; for (let [index, elem] of arr.entries()) { console.log(index, ': ', elem); }

Spiega la differenza tra oggetti mutabili e immutabili.

L'immutabilità è un principio fondamentale nella programmazione funzionale, e ha molto da offrire anche ai programmi orientati agli oggetti. Un oggetto mutabile è un oggetto il cui stato può essere modificato dopo la sua creazione. Un oggetto immutabile è un oggetto il cui stato non può essere modificato dopo la sua creazione.

Qual è un esempio di oggetto immutabile in JavaScript?

In JavaScript, alcuni tipi incorporati (numeri, stringhe) sono immutabili, ma gli oggetti personalizzati sono generalmente mutabili.

Alcuni oggetti JavaScript immutabili incorporati sono 'Math', 'Date'.

Ecco alcuni modi per aggiungere/simulare l'immutabilità su semplici oggetti JavaScript.

Proprietà costanti dell'oggetto

Combinando 'writable: false' e 'configurable: false', puoi essenzialmente creare una costante (non modificabile, ridefinibile o eliminabile) come proprietà di un oggetto, come:

let myObject = {}; Object.defineProperty(myObject, 'number', { value: 42, writable: false, configurable: false, }); console.log(myObject.number); // 42 myObject.number = 43; console.log(myObject.number); // 42

Impedire le estensioni

Se vuoi impedire che a un oggetto vengano aggiunte nuove proprietà, ma lasciare intatte le altre proprietà dell'oggetto, chiama 'Object.preventExtensions(...)':

var myObject = { a: 2, }; Object.preventExtensions(myObject); myObject.b = 3; myObject.b; // undefined

In modalità non rigorosa, la creazione di 'b' fallisce silenziosamente. In modalità rigorosa, genera un 'TypeError'.

Seal

'Object.seal()' crea un oggetto "sigillato", il che significa che prende un oggetto esistente e sostanzialmente chiama 'Object.preventExtensions()' su di esso, ma contrassegna anche tutte le sue proprietà esistenti come 'configurable: false'.

Quindi, non solo non puoi aggiungere altre proprietà, ma non puoi nemmeno riconfigurare o eliminare alcuna proprietà esistente (anche se puoi ancora modificarne i valori).

Freeze

'Object.freeze()' crea un oggetto congelato, il che significa che prende un oggetto esistente e sostanzialmente chiama 'Object.seal()' su di esso, ma contrassegna anche tutte le proprietà "data accessor" come 'writable: false', in modo che i loro valori non possano essere modificati.

Questo approccio è il livello più alto di immutabilità che puoi ottenere per un oggetto stesso, poiché impedisce qualsiasi modifica all'oggetto o a qualsiasi delle sue proprietà dirette (anche se, come menzionato sopra, il contenuto di qualsiasi altro oggetto a cui si fa riferimento non viene influenzato).

var immutable = Object.freeze({});

Congelare un oggetto non consente di aggiungere nuove proprietà a un oggetto e impedisce di rimuovere o alterare le proprietà esistenti. 'Object.freeze()' preserva l'enumerabilità, la configurabilità, la scrivibilità e il prototipo dell'oggetto. Restituisce l'oggetto passato e non crea una copia congelata.

Quali sono i pro e i contro dell'immutabilità?

Pro

  • Rilevamento delle modifiche più semplice - L'uguaglianza degli oggetti può essere determinata in modo performante e semplice tramite l'uguaglianza referenziale. Questo è utile per confrontare le differenze tra gli oggetti in React e Redux.
  • I programmi con oggetti immutabili sono meno complicati da pensare, poiché non devi preoccuparti di come un oggetto può evolvere nel tempo.
  • Le copie difensive non sono più necessarie quando gli oggetti immutabili vengono restituiti da o passati a funzioni, poiché non c'è possibilità che un oggetto immutabile venga modificato da essa.
  • Facile condivisione tramite riferimenti - Una copia di un oggetto è buona quanto un'altra, quindi puoi memorizzare nella cache gli oggetti o riutilizzare lo stesso oggetto più volte.
  • Thread-safe - Gli oggetti immutabili possono essere usati in sicurezza tra i thread in un ambiente multi-thread poiché non c'è il rischio che vengano modificati in altri thread in esecuzione contemporaneamente.
  • Utilizzando librerie come ImmutableJS, gli oggetti vengono modificati usando la condivisione strutturale e è necessaria meno memoria per avere più oggetti con strutture simili.

Contro

  • Implementazioni ingenue di strutture dati immutabili e delle sue operazioni possono comportare prestazioni estremamente scarse perché vengono creati nuovi oggetti ogni volta. Si consiglia di utilizzare librerie per strutture dati immutabili efficienti e operazioni che sfruttano la condivisione strutturale.
  • L'allocazione (e la deallocazione) di molti piccoli oggetti piuttosto che la modifica di quelli esistenti può causare un impatto sulle prestazioni. La complessità dell'allocatore o del garbage collector di solito dipende dal numero di oggetti nell'heap.
  • Le strutture dati cicliche come i grafi sono difficili da costruire. Se hai due oggetti che non possono essere modificati dopo l'inizializzazione, come puoi farli puntare l'uno all'altro?

Come puoi ottenere l'immutabilità nel tuo codice?

L'alternativa è usare dichiarazioni 'const' combinate con le tecniche menzionate sopra per la creazione. Per gli oggetti "mutanti", usa l'operatore 'spread', 'Object.assign', 'Array.concat()', ecc., per creare nuovi oggetti invece di mutare l'oggetto originale.

Esempi:

// Esempio di array const arr = [1, 2, 3]; const newArr = [...arr, 4]; // [1, 2, 3, 4] // Esempio di oggetto const human = Object.freeze({ race: 'human' }); const john = { ...human, name: 'John' }; // {race: "human", name: "John"} const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}

Spiega la differenza tra funzioni sincrone e asincrone.

Le funzioni sincrone sono bloccanti mentre quelle asincrone no. Nelle funzioni sincrone, le istruzioni vengono completate prima che venga eseguita l'istruzione successiva. In questo caso, il programma viene valutato esattamente nell'ordine delle istruzioni e l'esecuzione del programma viene sospesa se una delle istruzioni richiede molto tempo.

Le funzioni asincrone di solito accettano una callback come parametro e l'esecuzione continua sulla riga successiva immediatamente dopo che la funzione asincrona è stata invocata. La callback viene invocata solo quando l'operazione asincrona è completata e lo stack di chiamate è vuoto. Operazioni pesanti come il caricamento di dati da un server web o l'interrogazione di un database dovrebbero essere eseguite in modo asincrono in modo che il thread principale possa continuare a eseguire altre operazioni invece di bloccarsi fino al completamento di quella lunga operazione (nel caso dei browser, l'interfaccia utente si bloccherà).

Cos'è l''event loop'? Qual è la differenza tra 'call stack' e 'task queue'?

L''event loop' è un ciclo a thread singolo che monitora lo 'call stack' e controlla se c'è del lavoro da fare nella 'task queue'. Se lo 'call stack' è vuoto e ci sono funzioni di callback nella 'task queue', una funzione viene rimossa dalla coda e spinta nello 'call stack' per essere eseguita.

Se non hai ancora visto il discorso di Philip Robert sull'Event Loop, dovresti farlo. È uno dei video più visti su JavaScript.

Spiega le differenze sull'uso di 'foo' tra 'function foo() {}' e 'var foo = function() {}'

Il primo è una dichiarazione di funzione mentre il secondo è un'espressione di funzione. La differenza fondamentale è che le dichiarazioni di funzione hanno il loro corpo sottoposto a 'hoisting' ma i corpi delle espressioni di funzione no (hanno lo stesso comportamento di 'hoisting' delle variabili). Per maggiori spiegazioni sull''hoisting', fare riferimento alla domanda precedente sull'hoisting. Se si tenta di invocare un'espressione di funzione prima che sia definita, si otterrà un errore 'Uncaught TypeError: XXX is not a function'.

Dichiarazione di funzione

foo(); // 'FOOOOO' function foo() { console.log('FOOOOO'); }

Espressione di funzione

foo(); // Uncaught TypeError: foo is not a function var foo = function () { console.log('FOOOOO'); };

Quali sono le differenze tra le variabili create usando 'let', 'var' o 'const'?

Le variabili dichiarate usando la parola chiave 'var' sono limitate alla funzione in cui sono create, o se create al di fuori di qualsiasi funzione, all'oggetto globale. 'let' e 'const' sono ambito di blocco, il che significa che sono accessibili solo all'interno del più vicino set di parentesi graffe (funzione, blocco if-else o ciclo for).

function foo() { // Tutte le variabili sono accessibili all'interno delle funzioni. var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; console.log(bar); // bar console.log(baz); // baz console.log(qux); // qux } console.log(bar); // ReferenceError: bar non è definito console.log(baz); // ReferenceError: baz non è definito console.log(qux); // ReferenceError: qux non è definito
if (true) { var bar = 'bar'; let baz = 'baz'; const qux = 'qux'; } // Le variabili dichiarate con var sono accessibili ovunque nell'ambito della funzione. console.log(bar); // bar // Le variabili definite con let e const non sono accessibili al di fuori del blocco in cui sono state definite. console.log(baz); // ReferenceError: baz non è definito console.log(qux); // ReferenceError: qux non è definito

'var' consente di sollevare le variabili ('hoisting'), il che significa che possono essere referenziate nel codice prima di essere dichiarate. 'let' e 'const' non lo consentiranno, generando invece un errore.

console.log(foo); // undefined var foo = 'foo'; console.log(baz); // ReferenceError: impossibile accedere alla dichiarazione lessicale 'baz' prima dell'inizializzazione let baz = 'baz'; console.log(bar); // ReferenceError: impossibile accedere alla dichiarazione lessicale 'bar' prima dell'inizializzazione const bar = 'bar';

Rideclarare una variabile con 'var' non genererà un errore, ma 'let' e 'const' sì.

var foo = 'foo'; var foo = 'bar'; console.log(foo); // "bar" let baz = 'baz'; let baz = 'qux'; // Uncaught SyntaxError: L'identificatore 'baz' è già stato dichiarato

'let' e 'const' differiscono in quanto 'let' consente di riassegnare il valore della variabile mentre 'const' no.

// Va bene. let foo = 'foo'; foo = 'bar'; // Questo causa un'eccezione. const baz = 'baz'; baz = 'qux';

Quali sono le differenze tra la classe ES6 e i costruttori di funzioni ES5?

Vediamo prima un esempio di ciascuno:

// Costruttore di funzione ES5 function Person(name) { this.name = name; } // Classe ES6 class Person { constructor(name) { this.name = name; } }

Per i costruttori semplici, sembrano piuttosto simili.

La differenza principale nel costruttore si presenta quando si usa l'ereditarietà. Se vogliamo creare una classe 'Student' che sottoclassa 'Person' e aggiungere un campo 'studentId', questo è ciò che dobbiamo fare in aggiunta a quanto sopra.

// Costruttore di funzione ES5 function Student(name, studentId) { // Chiama il costruttore della superclasse per inizializzare i membri derivati dalla superclasse. Person.call(this, name); // Inizializza i membri propri della sottoclasse. this.studentId = studentId; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; // Classe ES6 class Student extends Person { constructor(name, studentId) { super(name); this.studentId = studentId; } }

È molto più prolisso usare l'ereditarietà in ES5 e la versione ES6 è più facile da capire e ricordare.

Puoi offrire un caso d'uso per la nuova sintassi della funzione 'arrow =>'? In che modo questa nuova sintassi differisce dalle altre funzioni?

Un evidente vantaggio delle funzioni freccia è semplificare la sintassi necessaria per creare funzioni, senza la necessità della parola chiave 'function'. Il 'this' all'interno delle funzioni freccia è anche legato all'ambito racchiudente, il che è diverso rispetto alle funzioni normali dove il 'this' è determinato dall'oggetto che le chiama. Il 'this' con ambito lessicale è utile quando si invocano callback, specialmente nei componenti React.

Qual è il vantaggio dell'utilizzo della sintassi 'arrow' per un metodo in un costruttore?

Il vantaggio principale dell'utilizzo di una funzione freccia come metodo all'interno di un costruttore è che il valore di 'this' viene impostato al momento della creazione della funzione e non può cambiare in seguito. Quindi, quando il costruttore viene utilizzato per creare un nuovo oggetto, 'this' farà sempre riferimento a quell'oggetto. Ad esempio, supponiamo di avere un costruttore 'Person' che prende un nome come argomento e ha due metodi per 'console.log' quel nome, uno come funzione normale e uno come funzione freccia:

const Person = function (firstName) { this.firstName = firstName; this.sayName1 = function () { console.log(this.firstName); }; this.sayName2 = () => { console.log(this.firstName); }; }; const john = new Person('John'); const dave = new Person('Dave'); john.sayName1(); // John john.sayName2(); // John // Il valore 'this' della funzione normale può essere modificato, ma quello della funzione freccia no john.sayName1.call(dave); // Dave (perché "this" ora è l'oggetto dave) john.sayName2.call(dave); // John john.sayName1.apply(dave); // Dave (perché 'this' ora è l'oggetto dave) john.sayName2.apply(dave); // John john.sayName1.bind(dave)(); // Dave (perché 'this' ora è l'oggetto dave) john.sayName2.bind(dave)(); // John var sayNameFromWindow1 = john.sayName1; sayNameFromWindow1(); // undefined (perché 'this' ora è l'oggetto window) var sayNameFromWindow2 = john.sayName2; sayNameFromWindow2(); // John

Il punto chiave qui è che 'this' può essere modificato per una funzione normale, ma il contesto rimane sempre lo stesso per una funzione freccia. Quindi, anche se passi la tua funzione freccia a diverse parti della tua applicazione, non dovrai preoccuparti che il contesto cambi.

Questo può essere particolarmente utile nei componenti di classe React. Se definisci un metodo di classe per qualcosa come un gestore di clic usando una funzione normale, e poi passi quel gestore di clic a un componente figlio come prop, dovrai anche legare 'this' nel costruttore del componente genitore. Se invece usi una funzione freccia, non è necessario legare 'this', poiché il metodo otterrà automaticamente il suo valore 'this' dal suo contesto lessicale racchiudente.

Qual è la definizione di una funzione di ordine superiore?

Una funzione di ordine superiore è qualsiasi funzione che prende una o più funzioni come argomenti, che utilizza per operare su alcuni dati, e/o restituisce una funzione come risultato. Le funzioni di ordine superiore sono destinate ad astrarre alcune operazioni che vengono eseguite ripetutamente. L'esempio classico è 'map', che prende un array e una funzione come argomenti. 'map' quindi usa questa funzione per trasformare ogni elemento nell'array, restituendo un nuovo array con i dati trasformati. Altri esempi popolari in JavaScript sono 'forEach', 'filter' e 'reduce'. Una funzione di ordine superiore non ha solo bisogno di manipolare array poiché ci sono molti casi d'uso per restituire una funzione da un'altra funzione. 'Function.prototype.bind' è un tale esempio in JavaScript.

Map

Diciamo di avere un array di nomi che dobbiamo trasformare ogni stringa in maiuscolo.

const names = ['irish', 'daisy', 'anna'];

Il modo imperativo sarà il seguente:

const transformNamesToUppercase = function (names) { const results = []; for (let i = 0; i < names.length; i++) { results.push(names[i].toUpperCase()); } return results; }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

L'uso di '.map(transformerFn)' rende il codice più breve e più dichiarativo.

const transformNamesToUppercase = function (names) { return names.map((name) => name.toUpperCase()); }; transformNamesToUppercase(names); // ['IRISH', 'DAISY', 'ANNA']

Puoi fornire un esempio di destrutturazione di un oggetto o di un array?

La destrutturazione è un'espressione disponibile in ES6 che consente un modo conciso e conveniente per estrarre i valori di Oggetti o Array e inserirli in variabili distinte.

Destrutturazione di array

// Assegnazione di variabili. const foo = ['one', 'two', 'three']; const [one, two, three] = foo; console.log(one); // "one" console.log(two); // "two" console.log(three); // "three"
// Scambio di variabili let a = 1; let b = 3; [a, b] = [b, a]; console.log(a); // 3 console.log(b); // 1

Destrutturazione di oggetti

// Assegnazione di variabili. const o = { p: 42, q: true }; const { p, q } = o; console.log(p); // 42 console.log(q); // true

Le 'ES6 Template Literals' offrono molta flessibilità nella generazione di stringhe, puoi darne un esempio?

I 'template literals' aiutano a semplificare l'interpolazione di stringhe o l'inclusione di variabili in una stringa. Prima di ES2015, era comune fare qualcosa del genere:

var person = { name: 'Tyler', age: 28 }; console.log( 'Ciao, il mio nome è ' + person.name + ' e ho ' + person.age + ' anni!', ); // 'Ciao, il mio nome è Tyler e ho 28 anni!'

Con i 'template literals', ora puoi creare lo stesso output in questo modo:

const person = { name: 'Tyler', age: 28 }; console.log(`Ciao, il mio nome è ${person.name} e ho ${person.age} anni!`); // 'Ciao, il mio nome è Tyler e ho 28 anni!'

Nota che usi i 'backtick', non le virgolette, per indicare che stai usando un 'template literal' e che puoi inserire espressioni all'interno dei segnaposto '${}'.

Un secondo caso d'uso utile è la creazione di stringhe su più righe. Prima di ES2015, potevi creare una stringa su più righe in questo modo:

console.log('Questa è la riga uno.\nQuesta è la riga due.'); // Questa è la riga uno. // Questa è la riga due.

Oppure se volevi dividerla in più righe nel tuo codice per non dover scorrere a destra nel tuo editor di testo per leggere una stringa lunga, potevi anche scriverla in questo modo:

console.log('Questa è la riga uno.\n' + 'Questa è la riga due.'); // Questa è la riga uno. // Questa è la riga due.

I 'template literals', tuttavia, conservano qualsiasi spaziatura tu aggiunga loro. Ad esempio, per creare lo stesso output su più righe che abbiamo creato sopra, puoi semplicemente fare:

console.log(`Questa è la riga uno. Questa è la riga due.`); // Questa è la riga uno. // Questa è la riga due.

Un altro caso d'uso dei 'template literals' sarebbe quello di usarli come sostituti delle librerie di templating per semplici interpolazioni di variabili:

const person = { name: 'Tyler', age: 28 }; document.body.innerHTML = ` <div> <p>Nome: ${person.name}</p> <p>Età: ${person.age}</p> </div> `;

Nota che il tuo codice potrebbe essere suscettibile a XSS usando '.innerHTML'. Sanifica i tuoi dati prima di visualizzarli se provengono da un utente!

Puoi dare un esempio di funzione di 'currying' e perché questa sintassi offre un vantaggio?

Il 'currying' è un pattern in cui una funzione con più di un parametro viene suddivisa in più funzioni che, quando chiamate in serie, accumuleranno tutti i parametri richiesti uno alla volta. Questa tecnica può essere utile per rendere il codice scritto in stile funzionale più facile da leggere e comporre. È importante notare che affinché una funzione sia 'curried', deve iniziare come una singola funzione, quindi essere suddivisa in una sequenza di funzioni che accettano ciascuna un parametro.

function curry(fn) { if (fn.length === 0) { return fn; } function _curried(depth, args) { return function (newArgument) { if (depth - 1 === 0) { return fn(...args, newArgument); } return _curried(depth - 1, [...args, newArgument]); }; } return _curried(fn.length, []); } function add(a, b) { return a + b; } var curriedAdd = curry(add); var addFive = curriedAdd(5); var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

Quali sono i vantaggi dell'utilizzo della sintassi 'spread' e in cosa differisce dalla sintassi 'rest'?

La sintassi 'spread' di ES6 è molto utile quando si programma in un paradigma funzionale poiché possiamo facilmente creare copie di array o oggetti senza ricorrere a 'Object.create', 'slice' o una funzione di libreria. Questa funzionalità del linguaggio è usata spesso nei progetti Redux e RxJS.

function putDookieInAnyArray(arr) { return [...arr, 'dookie']; } const result = putDookieInAnyArray(['I', 'really', "don't", 'like']); // ["I", "really", "don't", "like", "dookie"] const person = { name: 'Todd', age: 29, }; const copyOfTodd = { ...person };

La sintassi 'rest' di ES6 offre una scorciatoia per includere un numero arbitrario di argomenti da passare a una funzione. È come un'inversione della sintassi 'spread', prendendo dati e inserendoli in un array piuttosto che disimballare un array di dati, e funziona negli argomenti di funzione, così come nelle assegnazioni di destrutturazione di array e oggetti.

function addFiveToABunchOfNumbers(...numbers) { return numbers.map((x) => x + 5); } const result = addFiveToABunchOfNumbers(4, 5, 6, 7, 8, 9, 10); // [9, 10, 11, 12, 13, 14, 15] const [a, b, ...rest] = [1, 2, 3, 4]; // a: 1, b: 2, rest: [3, 4] const { e, f, ...others } = { e: 1, f: 2, g: 3, h: 4, }; // e: 1, f: 2, others: { g: 3, h: 4 }

Come si può condividere il codice tra i file?

Dipende dall'ambiente JavaScript.

Sul client (ambiente browser), finché le variabili/funzioni sono dichiarate nell'ambito globale ('window'), tutti gli script possono farvi riferimento. In alternativa, adotta la 'Asynchronous Module Definition' (AMD) tramite RequireJS per un approccio più modulare.

Sul server (Node.js), il modo comune è stato quello di usare CommonJS. Ogni file è trattato come un modulo e può esportare variabili e funzioni allegandole all'oggetto 'module.exports'.

ES2015 definisce una sintassi di moduli che mira a sostituire sia AMD che CommonJS. Questa sarà eventualmente supportata sia negli ambienti browser che Node.

Perché si vorrebbe creare membri di classe statici?

I membri di classe statici (proprietà/metodi) non sono legati a un'istanza specifica di una classe e hanno lo stesso valore indipendentemente dall'istanza che vi fa riferimento. Le proprietà statiche sono tipicamente variabili di configurazione e i metodi statici sono solitamente funzioni di utilità pure che non dipendono dallo stato dell'istanza.