Al giorno d'oggi la programmazione funzionale ha fatto un bel salto nel mondo dello sviluppo. E per una buona ragione: le tecniche funzionali possono aiutarti a scrivere più codice dichiarativo che è più facile da capire a colpo d'occhio, refactoring e test.
Uno dei capisaldi della programmazione funzionale è l'uso speciale delle liste e delle operazioni di lista. E quelle cose sono esattamente ciò che il suono è come sono: matrici di cose e cose che fai a loro. Ma la mentalità funzionale li tratta un po 'diversamente da come ci si potrebbe aspettare.
Questo articolo darà un'occhiata da vicino a ciò che mi piace chiamare le operazioni "big three": carta geografica
, filtro
, e ridurre
. Avvolgere la testa attorno a queste tre funzioni è un passo importante verso la possibilità di scrivere codice funzionale pulito e apre le porte alle tecniche di programmazione funzionale e reattiva estremamente potenti.
Significa anche che non dovrai mai scrivere a per
loop di nuovo.
Curioso? Tuffiamoci dentro.
Spesso, ci troviamo a dover prendere un array e modificare ogni elemento in esso esattamente nello stesso modo. Esempi tipici di questo sono la quadratura di ogni elemento in una matrice di numeri, il recupero del nome da un elenco di utenti o l'esecuzione di un'espressione regolare contro una serie di stringhe.
carta geografica
è un metodo costruito per fare esattamente questo. È definito su Array.prototype
, quindi puoi chiamarlo su qualsiasi array e accetta un callback come primo argomento.
Quando chiami carta geografica
su un array, esegue tale callback su ogni elemento al suo interno, restituendo a nuovo array con tutti i valori restituiti dal callback.
Sotto il cappuccio, carta geografica
passa tre argomenti alla tua richiamata:
Diamo un'occhiata ad un codice.
carta geografica
in praticaSupponiamo di avere un'app che mantiene una serie di compiti per il giorno. Ogni compito
è un oggetto, ciascuno con a nome
e durata
proprietà:
// Le durate sono in minuti var tasks = ['name': 'Write for Envato Tuts +', 'duration': 120, 'name': 'Work out', 'duration': 60, 'name' : 'Procrastinate on Duolingo', 'duration': 240];
Diciamo che vogliamo creare un nuovo array con solo il nome di ogni attività, in modo da poter dare un'occhiata a tutto ciò che abbiamo fatto oggi. Usare un per
loop, scriveremmo qualcosa del genere:
var task_names = []; per (var i = 0, max = tasks.length; i < max; i += 1) task_names.push(tasks[i].name);
JavaScript offre anche a per ciascuno
ciclo continuo. Funziona come a per
loop, ma gestisce tutta la confusione di controllare il nostro indice di loop rispetto alla lunghezza dell'array per noi:
var task_names = []; tasks.forEach (function (task) task_names.push (task.name););
utilizzando carta geografica
, possiamo scrivere:
var task_names = tasks.map (function (task, index, array) return task.name;);
Includo il indice
e schieramento
parametri per ricordarti che sono lì se ne hai bisogno. Dato che non li ho usati qui, però, potresti lasciarli fuori, e il codice funzionerebbe bene.
Ci sono alcune importanti differenze tra i due approcci:
carta geografica
, non devi gestire lo stato di per
loop te stesso.Spingere
dentro. carta geografica
restituisce il prodotto finito tutto in una volta, quindi possiamo semplicemente assegnare il valore di ritorno ad una nuova variabile.ritorno
dichiarazione nel tuo callback. Se non lo fai, avrai un nuovo array pieno di non definito
. Si scopre, tutti delle funzioni che vedremo oggi condividono queste caratteristiche.
Il fatto che non dobbiamo gestire manualmente lo stato del loop rende il nostro codice più semplice e più gestibile. Il fatto che possiamo operare direttamente sull'elemento invece di dover indicizzare nell'array rende le cose più leggibili.
Usare un per ciascuno
loop risolve entrambi questi problemi per noi. Ma carta geografica
ha ancora almeno due distinti vantaggi:
per ciascuno
ritorna non definito
, quindi non concatena con altri metodi di array. carta geografica
restituisce un array, quindi tu può associarlo ad altri metodi di array.carta geografica
ritornauna matrice con il prodotto finito, piuttosto che richiederci di mutare una matrice all'interno del ciclo. Mantenere il numero di luoghi in cui si modifica lo stato al minimo assoluto è un importante principio della programmazione funzionale. Rende il codice più sicuro e più comprensibile.
Ora è anche il momento giusto per far notare che se si è nel nodo, testando questi esempi nella console del browser Firefox o usando Babel o Traceur, è possibile scrivere questo in modo più conciso con le funzioni freccia ES6:
var task_names = tasks.map ((task) => task.name);
Le funzioni della freccia lasciano uscire il ritorno
parola chiave in one-liner.
Non diventa molto più leggibile di così.
Il callback a cui si passa carta geografica
deve avere un esplicito ritorno
dichiarazione, o carta geografica
sputerà un array pieno di non definito
. Non è difficile ricordare di includere un ritorno
valore, ma non è difficile da dimenticare.
Se tu fare dimenticare, carta geografica
non si lamenteranno Invece, restituirà tranquillamente un array pieno di nulla. Errori silenziosi come quello possono essere sorprendentemente difficili da debugare.
Fortunatamente, questo è il solo Gotcha con carta geografica
. Ma è una trappola abbastanza comune che sono obbligato a sottolineare: Assicurati sempre che la tua callback contenga un ritorno
dichiarazione!
Leggere le implementazioni è una parte importante della comprensione. Quindi, scriviamo il nostro leggero carta geografica
per capire meglio cosa sta succedendo sotto il cofano. Se vuoi vedere un'implementazione di qualità di produzione, controlla il polyfill di Mozilla su MDN.
var map = function (array, callback) var new_array = []; array.forEach (function (element, index, array) new_array.push (callback (elemento));); return new_array; ; var task_names = map (tasks, function (task) return task.name;);
Questo codice accetta un array e una funzione di callback come argomenti. Quindi crea una nuova matrice; esegue il callback su ogni elemento dell'array che abbiamo passato; spinge i risultati nel nuovo array; e restituisce il nuovo array. Se lo esegui nella tua console, otterrai lo stesso risultato di prima. Assicurati di aver inizializzato compiti
prima di testarlo!
Mentre usiamo un ciclo for sotto il cofano, il wrapping in una funzione nasconde i dettagli e ci lascia lavorare invece con l'astrazione.
Ciò rende il nostro codice più dichiarativo - dice che cosa fare, no Come per farlo. Apprezzerai quanto più leggibile, manutenibile e, erm, debuggable questo può rendere il tuo codice.
Il prossimo delle nostre operazioni con gli array è filtro
. Fa esattamente quello che sembra: prende un array e filtra elementi indesiderati.
Piace carta geografica
, filtro
è definito su Array.prototype
. È disponibile su qualsiasi array e si passa a un callback come primo argomento. filtro
esegue quella richiamata su ogni elemento dell'array e sputa a nuovo array contenente solo gli elementi per cui è stato restituito il callback vero
.
Piace anche a carta geografica
, filtro
passa i tre argomenti di callback:
filtro
soprafiltro
in praticaRivediamo il nostro esempio di attività. Invece di estrarre i nomi di ogni attività, diciamo che voglio ottenere un elenco delle attività che mi hanno richiesto due o più ore per essere completato.
utilizzando per ciascuno
, scriveremmo:
var difficult_tasks = []; tasks.forEach (function (task) if (task.duration> = 120) difficult_tasks.push (task););
Con filtro
:
var difficult_tasks = tasks.filter (function (task) return task.duration> = 120;); // Utilizzo di ES6 var difficult_tasks = tasks.filter ((task) => task.duration> = 120);
Qui, sono andato avanti e ho lasciato fuori il indice
e schieramento
argomenti alla nostra callback, dal momento che non li usiamo.
Proprio come carta geografica
, filtro
ci permette di:
per ciascuno
o per
ciclo continuoIl callback a cui si passa carta geografica
deve includere una dichiarazione di ritorno se vuoi che funzioni correttamente. Con filtro
, devi anche includere una dichiarazione di reso, e tu dovere assicurati che restituisca un valore booleano.
Se si dimentica la dichiarazione di reso, la richiamata verrà restituita non definito
, quale filtro
costringerà a falso
. Invece di lanciare un errore, restituisce automaticamente un array vuoto!
Se vai sull'altra rotta e restituisci qualcosa che è non è esplicitamente vero
o falso
, poi filtro
cercherà di capire cosa intendi applicando le regole di coercizione di JavaScript. Più spesso, questo è un bug. E, proprio come dimenticare la tua dichiarazione di ritorno, sarà silenziosa.
Sempre assicurati che le tue callback includano un'istruzione di reso esplicita. E sempre assicurati di avere le tue callback in filtro
ritorno vero
o falso
. La tua sanità mentale ti ringrazierà.
Ancora una volta, il modo migliore per capire un pezzo di codice è ... beh, per scriverlo. Facciamo rotolare il nostro peso leggero filtro
. I bravi ragazzi di Mozilla hanno anche un polyfill per la forza industriale da leggere.
var filter = function (array, callback) var filtered_array = []; array.forEach (function (element, index, array) if (callback (elemento, indice, array)) filtered_array.push (elemento);); return filtered_array; ;
carta geografica
crea un nuovo array trasformando ogni elemento in un array, individualmente. filtro
crea una nuova matrice rimuovendo gli elementi che non appartengono. ridurre
, d'altra parte, prende tutti gli elementi in un array, e riduce loro in un unico valore.
Proprio come carta geografica
e filtro
, ridurre
è definito su Array.prototype
e così disponibile su qualsiasi array, e si passa un callback come primo argomento. Ma ci vuole anche un secondo argomento opzionale: il valore per iniziare a combinare tutti gli elementi dell'array in.
ridurre
passa i tuoi quattro argomenti di callback:
ridurre
sopraSi noti che la richiamata ottiene a valore precedente a ogni iterazione. Alla prima iterazione, lì è nessun valore precedente. Questo è il motivo per cui hai la possibilità di passare ridurre
un valore iniziale: agisce come "valore precedente" per la prima iterazione, quando altrimenti non lo sarebbe.
Infine, tieni presente questo ridurre
restituisce a valore singolo, non una matrice contenente un singolo oggetto. Questo è più importante di quanto possa sembrare, e tornerò ad esso negli esempi.
ridurre
in praticaDa ridurre
è la funzione che le persone trovano più aliene all'inizio, inizieremo camminando passo dopo passo attraverso qualcosa di semplice.
Diciamo che vogliamo trovare la somma di un elenco di numeri. Usando un ciclo, che assomiglia a questo:
var numbers = [1, 2, 3, 4, 5], total = 0; numbers.forEach (funzione (numero) total + = numero;);
Anche se questo non è un cattivo uso per per ciascuno
, ridurre
ha ancora il vantaggio di permetterci di evitare la mutazione. Con ridurre
, scriveremmo:
var total = [1, 2, 3, 4, 5] .reduce (funzione (precedente, corrente) return previous + current;, 0);
Per prima cosa, chiamiamo ridurre
sulla nostra lista di numeri.
Passiamo ad esso un callback, che accetta il valore precedente e il valore corrente come argomenti, e restituisce il risultato di aggiungerli insieme. Da quando siamo passati 0
come secondo argomento a ridurre
, lo userà come valore di precedente
alla prima iterazione.
Se lo facciamo passo dopo passo, assomiglia a questo:
Iterazione | Precedente | attuale | Totale |
---|---|---|---|
1 | 0 | 1 | 1 |
2 | 1 | 2 | 3 |
3 | 3 | 3 | 6 |
4 | 6 | 4 | 10 |
5 | 10 | 5 | 15 |
Se non sei un fan delle tabelle, esegui questo snippet nella console:
var total = [1, 2, 3, 4, 5] .reduce (funzione (precedente, corrente, indice) var val = precedente + corrente; console.log ("Il valore precedente è" + precedente + "; il valore è "+ current +" e l'iterazione corrente è "+ (index + 1)); return val;, 0); console.log ("Il ciclo è terminato e il valore finale è" + total + ".");
Per ricapitolare: ridurre
itera su tutti gli elementi di un array, combinandoli come specificato nel callback. Ad ogni iterazione, la richiamata ha accesso a precedente valore, qual è total-so-far, o valore accumulato; il valore corrente; il indice corrente; e il tutto schieramento, se ne hai bisogno.
Torniamo all'esempio dei nostri compiti. Abbiamo ottenuto un elenco di nomi di attività da carta geografica
, e un elenco filtrato di compiti che ha richiesto molto tempo con ... beh, filtro
.
Cosa succederebbe se volessimo sapere il tempo totale che abbiamo trascorso lavorando oggi?
Usare un per ciascuno
loop, dovresti scrivere:
var total_time = 0; tasks.forEach (function (task) // Il segno più costruisce solo // task.duration da una stringa a un numero total_time + = (+ task.duration););
Con ridurre
, ciò diventa:
var total_time = tasks.reduce (function (precedente, current) return previous + current;, 0); // Utilizzo delle funzioni freccia var total_time = tasks.reduce ((precedente, corrente) precedente + corrente);
Facile.
Questo è quasi tutto quello che c'è da fare. Quasi, perché JavaScript ci fornisce un altro metodo poco conosciuto, chiamato reduceRight
. Negli esempi sopra, ridurre
iniziato al primo elemento nell'array, iterando da sinistra a destra:
var array_of_arrays = [[1, 2], [3, 4], [5, 6]]; var concatenated = array_of_arrays.reduce (function (precedente, current) return previous.concat (current);); console.log (concatenati); // [1, 2, 3, 4, 5, 6];
reduceRight
fa la stessa cosa, ma nella direzione opposta:
var array_of_arrays = [[1, 2], [3, 4], [5, 6]]; var concatenated = array_of_arrays.reduceRight (funzione (precedente, corrente) return previous.concat (current);); console.log (concatenati); // [5, 6, 3, 4, 1, 2];
Io uso ridurre
ogni giorno, ma non ho mai avuto bisogno reduceRight
. Suppongo che probabilmente non lo farai neanche. Ma se mai lo fai, ora sai che è lì.
I tre grandi trucchi con ridurre
siamo:
ritorno
ridurre
restituisce un singolo valoreFortunatamente, i primi due sono facili da evitare. Decidere quale dovrebbe essere il tuo valore iniziale dipende da quello che stai facendo, ma ne prenderai il controllo rapidamente.
L'ultimo potrebbe sembrare un po 'strano. Se ridurre
restituisce sempre un solo valore, perché dovresti aspettarti un array?
Ci sono alcune buone ragioni per questo. Primo, ridurre
restituisce sempre un singolo valore, non sempre un singolo numero. Se si riduce una matrice di matrici, ad esempio, verrà restituito un singolo array. Se hai l'abitudine o la riduzione degli array, sarebbe giusto aspettarsi che un array contenente un singolo oggetto non sia un caso speciale.
Secondo, se ridurre
fatto restituire un array con un singolo valore, con cui naturalmente sarebbe piacevole carta geografica
e filtro
, e altre funzioni sugli array che probabilmente utilizzerai con esso.
Tempo per il nostro ultimo sguardo sotto il cofano. Come al solito, Mozilla ha un polyfill antiproiettile da ridurre se si desidera verificarlo.
var reduce = function (array, callback, initial) var accumulator = initial || 0; array.forEach (function (element) accumulator = callback (accumulator, array [i]);); accumulatore di ritorno; ;
Due cose da notare, qui:
accumulatore
invece di precedente
. Questo è quello che vedrai di solito in natura.accumulatore
un valore iniziale, se un utente ne fornisce uno, e di default su 0
, altrimenti. Questo è il vero ridurre
si comporta anche.A questo punto potresti non esserlo quello impressionato.
Giusto: carta geografica
, filtro
, e ridurre
, da soli, non sono terribilmente interessanti.
Dopotutto, il loro vero potere risiede nella loro concatenabilità.
Diciamo che voglio fare quanto segue:
Per prima cosa, definiamo i nostri compiti per lunedì e martedì:
var monday = ['name': 'Write a tutorial', 'duration': 180, 'name': 'Some web development', 'duration': 120]; var martedì = ['nome': 'Continua a scrivere quel tutorial', 'durata': 240, 'nome': 'Un po' di sviluppo web ',' durata ': 180, ' nome ':' Un intero molto di niente ',' durata ': 240]; var tasks = [lunedi, martedì];
E ora, la nostra trasformazione adorabile:
var result = tasks.reduce (function (accumulator, current) return accumulator.concat (current);). map (function (task) return (task.duration / 60);). filter (funzione (durata) return duration> = 2;). map (function (duration) return duration * 25;). reduce (function (accumulator, current) return [(+ accumulator) + (+ current)];). map (function (dollar_amount) return '$' + dollar_amount.toFixed (2);). reduce (function (formatted_dollar_amount) return formatted_dollar_amount;);
O, più concisamente:
// Concatena il nostro array 2D in una singola lista var result = tasks.reduce ((acc, current) => acc.concat (current)) // Estrai la durata dell'attività e converti i minuti in ore .map ((task) = > task.duration / 60) // Filtra tutte le attività che hanno richiesto meno di due ore. filtro ((durata) => durata> = 2) // Moltiplica la durata di ciascuna attività in base alla nostra tariffa oraria .map ((durata) = > duration * 25) // Combina le somme in un importo in dollari .reduce ((acc, current) => [(+ acc) + (+ current)]) // Converti in un importo in dollari "piuttosto stampato". map ((amount) => '$' + amount.toFixed (2)) // Estrai l'unico elemento dell'array ottenuto da map .reduce ((formatted_amount) => formatted_amount);
Se sei arrivato così lontano, questo dovrebbe essere piuttosto semplice. Ci sono due bit di stranezza da spiegare, però.
Per prima cosa, sulla linea 10, devo scrivere:
// Remoinder omitted reduce (function (accumulator, current) return [(+ accumulator) + (+ current_];)
Due cose da spiegare qui:
accumulatore
e attuale
costringere i loro valori ai numeri. Se non lo fai, il valore restituito sarà la stringa piuttosto inutile, "12510075100"
.ridurre
sputerà un singolo valore, non un array. Ciò finirebbe per gettare a TypeError
, perché puoi usare solo carta geografica
su un array! Il secondo bit che potrebbe renderti un po 'a disagio è l'ultimo ridurre
, vale a dire:
// Remainder omitted map (function (dollar_amount) return '$' + dollar_amount.toFixed (2);). Reduce (function (formatted_dollar_amount) return formatted_dollar_amount;);
Quella chiamata a carta geografica
restituisce un array contenente un singolo valore. Qui, chiamiamo ridurre
per estrarre quel valore.
L'altro modo per farlo sarebbe quello di rimuovere la chiamata per ridurre e indicizzare nell'array carta geografica
sputa fuori:
var result = tasks.reduce (function (accumulator, current) return accumulator.concat (current);). map (function (task) return (task.duration / 60);). filter (funzione (durata) return duration> = 2;). map (function (duration) return duration * 25;). reduce (function (accumulator, current) return [(+ accumulator) + (+ current)];). map (function (dollar_amount) return '$' + dollar_amount.toFixed (2);) [0];
Questo è perfettamente corretto. Se ti senti più a tuo agio nell'uso di un indice di array, vai avanti.
Ma ti incoraggio a non farlo. Uno dei modi più potenti per utilizzare queste funzioni è nel campo della programmazione reattiva, in cui non sarai libero di utilizzare gli indici di array. Dare il calcio a quell'abitudine ora renderà le tecniche di apprendimento reattivo molto più semplici.
Infine, vediamo come il nostro amico il per ciascuno
loop lo farebbe:
var concatenated = lunedi.concat (martedì), fees = [], formatted_sum, hourly_rate = 25, total_fee = 0; concatenated.forEach (function (task) var duration = task.duration / 60; if (duration> = 2) fees.push (duration * hourly_rate);); fees.forEach (funzione (a pagamento) total_fee + = fee); var formatted_sum = '$' + total_fee.toFixed (2);
Tollerabile, ma rumoroso.
In questo tutorial, hai imparato come carta geografica
, filtro
, e ridurre
lavoro; come usarli; e approssimativamente come sono implementati. Hai visto che tutti ti permettono di evitare lo stato di mutamento, che usa per
e per ciascuno
richiede loop e ora dovresti avere una buona idea di come concatenarli tutti insieme.
Ormai sono sicuro che sei desideroso di pratica e ulteriore lettura. Ecco i miei primi tre suggerimenti su dove andare avanti:
JavaScript è diventato una delle lingue di fatto di lavorare sul web. Non è senza le sue curve di apprendimento, e ci sono un sacco di quadri e librerie per tenerti occupato, pure. Se stai cercando ulteriori risorse da studiare o da utilizzare nel tuo lavoro, dai un'occhiata a ciò che abbiamo a disposizione sul mercato Envato.
Se vuoi saperne di più su questo genere di cose, controlla il mio profilo di volta in volta; prendimi su Twitter (@PelekeS); o colpisci il mio blog su http://peleke.me.
Domande, commenti o confusioni? Lasciali sotto, e farò del mio meglio per tornare a ciascuno individualmente.
Scopri JavaScript: la guida completa
Abbiamo creato una guida completa per aiutarti a imparare JavaScript, sia che tu stia appena iniziando come sviluppatore web o che desideri esplorare argomenti più avanzati.