Programarea funcțională a făcut destul de mult în lumea dezvoltării în aceste zile. Și pentru un motiv bun: Tehnicile funcționale vă pot ajuta să scrieți un cod declarativ mai clar, ușor de înțeles dintr-o privire, refăctor și test.
Una dintre pietrele de temelie ale programării funcționale este utilizarea specială a listelor și operațiunilor de listă. Și acele lucruri sunt exact ceea ce sunetul este ca și cum ar fi: o serie de lucruri și lucrurile pe care le faceți pentru ele. Dar mentalitatea funcțională le tratează un pic diferit decât v-ați aștepta.
Acest articol va arunca o privire atentă la ceea ce îmi place să numesc operațiunile din lista celor trei mari: Hartă
, filtru
, și reduce
. Înfășurarea capului în jurul acestor trei funcții reprezintă un pas important spre a putea scrie un cod funcțional curat și deschide ușile spre tehnicile extrem de puternice de programare funcțională și reactivă.
De asemenea, înseamnă că nu va trebui să scrieți niciodată pentru
buclă din nou.
Curios? Hai să ne aruncăm.
Adesea, ne aflăm nevoiți să luăm o matrice și să modificăm fiecare element în el exact în același mod. Exemple tipice de acest lucru sunt împărțirea fiecărui element într-o serie de numere, recuperarea numelui dintr-o listă de utilizatori sau rularea unui regex împotriva unui șir de șiruri de caractere.
Hartă
este o metodă construită pentru a face exact acest lucru. Este definit pe Array.prototype
, astfel încât să o puteți numi pe orice matrice și acceptă un apel invers ca prim argument.
Când sunați Hartă
pe o matrice, execută apelul pe fiecare element din el, returnând a nou array cu toate valorile pe care callback-ul a revenit.
Sub capotă, Hartă
transmite trei argumente la apelul dvs. de apel:
Să ne uităm la un cod.
Hartă
in practicaSă presupunem că avem o aplicație care păstrează o serie de sarcini pentru ziua respectivă. Fiecare sarcină
este un obiect, fiecare cu un Nume
și durată
proprietate:
// Duratele sunt în câteva minute var tasks = ['name': 'Scrie pentru Envato Tuts +', 'duration': 120, 'name': ' : "Procrastinate pe Duolingo", "durata": 240];
Să presupunem că vrem să creăm o nouă matrice cu numele fiecărei sarcini, astfel încât să putem arunca o privire la tot ce am făcut astăzi. Folosind un pentru
bucla, vom scrie ceva de genul:
var task_names = []; pentru (var i = 0, max = tasks.length; i < max; i += 1) task_names.push(tasks[i].name);
JavaScript oferă, de asemenea, a pentru fiecare
buclă. Funcționează ca a pentru
buclă, dar gestionează toate dezordinea de a verifica indicele bucla noastră împotriva lungimea matrice pentru noi:
var task_names = []; tasks.forEach (funcție (sarcină) task_names.push (task.name););
Utilizarea Hartă
, putem scrie:
var task_names = tasks.map (functie (task, index, array) retur task.name;);
Am inclus index
și mulțime
parametri pentru a vă reaminti că sunt acolo dacă aveți nevoie de ele. Din moment ce nu le-am folosit aici, le-ai putea lăsa afară, iar codul ar fugi bine.
Există câteva diferențe importante între cele două abordări:
Hartă
, nu trebuie să gestionați starea pentru
bucurați-vă singur.Apăsați
în ea. Hartă
returnează produsul finit într-un singur pas, astfel încât să putem atribui pur și simplu valoarea returnată unei noi variabile.întoarcere
în apelul dvs. Dacă nu, veți obține o nouă matrice completă nedefinit
. Se pare, toate din funcțiile pe care le vom analiza astăzi împărtășesc aceste caracteristici.
Faptul că nu trebuie să gestionăm manual starea buclei face ca codul nostru să fie mai simplu și mai ușor de întreținut. Faptul că putem opera direct asupra elementului, în loc să trebuiască să indexăm în matrice, face ca lucrurile să fie mai ușor de citit.
Folosind un pentru fiecare
buclă rezolvă ambele probleme pentru noi. Dar Hartă
are încă cel puțin două avantaje distincte:
pentru fiecare
se intoarce nedefinit
, astfel încât nu lanț cu alte metode de matrice. Hartă
returnează o matrice, deci tu poate sa lant cu alte metode de matrice.Hartă
se intoarceo matrice cu produsul finit, în loc să ne solicităm să mutați o matrice în bucla. Menținerea numărului de locuri în care modificați starea la un minim absolut este un principiu important al programării funcționale. Aceasta face ca un cod mai sigur și mai inteligibil.
Acum este, de asemenea, un moment bun pentru a sublinia că dacă sunteți în Nod, testați aceste exemple în consola browser-ului Firefox sau folosind Babel sau Traceur, puteți scrie mai concis cu funcțiile săgeată ES6:
var task_names = tasks.map ((task) => task.name);
Funcțiile cu săgeți ne lasă să renunțăm întoarcere
cuvânt cheie în unul-liners.
Nu este mult mai ușor de citit decât asta.
Apelul la care treci Hartă
trebuie să aibă un explicit întoarcere
declarație, sau Hartă
va scuipa o gamă plină de nedefinit
. Nu este greu să vă amintiți să includeți a întoarcere
valoare, dar nu este greu de uitat.
daca tu do a uita, Hartă
nu se va plânge. În schimb, va trimite în liniște o matrice plină de nimic. Silent erori de genul asta pot fi surprinzător de greu de depanat.
Din fericire, acesta este numai gotcha cu Hartă
. Dar este o capcană destul de comună pe care trebuie să o subliniez: Întotdeauna asigurați-vă că apelul dvs. conține un a întoarcere
afirmație!
Implementarea lecturii este o parte importantă a înțelegerii. Deci, să scriem propria noastră ușoară Hartă
pentru a înțelege mai bine ce se întâmplă sub capotă. Dacă doriți să vedeți o implementare de calitate a producției, verificați poliflul Mozilla de la MDN.
var hartă = funcție (array, callback) var new_array = []; array.forEach (funcție (element, index, array) new_array.push (callback (element));); retur nou_array; ; var task_names = hartă (sarcini, funcție (sarcină) return task.name;);
Acest cod acceptă ca argumente o matrice și o funcție de apel invers. Apoi creează o nouă matrice; execută apelul invers la fiecare element din matricea în care am trecut; împinge rezultatele în noua matrice; și returnează noua matrice. Dacă executați acest lucru în consola dvs., veți obține același rezultat ca înainte. Asigurați-vă că inițializați sarcini
înainte de a le testa!
În timp ce folosim o buclă pentru sub capotă, înfășurarea acesteia într-o funcție ascunde detaliile și ne permite să lucrăm cu abstractizarea.
Asta face codul nostru mai declarativ - se spune ce să facă, nu Cum să o facă. Veți aprecia cât de mult mai ușor de citit și mai ușor de întreținut, debuggable acest lucru poate face codul dvs..
Următoarea operațiune de array este filtru
. Ea face exact ceea ce pare: Este nevoie de o matrice și filtrează elemente nedorite.
Ca Hartă
, filtru
este definită pe Array.prototype
. Este disponibil pe orice matrice și îi transmiteți un apel ca primul său argument. filtru
execută apelul pe fiecare element al matricei și scutură a nou array conținând numai elementele pentru care apelul a revenit Adevărat
.
De asemenea, cum ar fi Hartă
, filtru
transmite apelul dvs. trei argumente:
filtru
pefiltru
in practicaSă revedem exemplul nostru de activitate. În loc să scoatem numele fiecărei sarcini, să spunem că vreau să obțin o listă a sarcinilor care mi-au luat două ore sau mai multe pentru a obține.
Utilizarea pentru fiecare
, am scrie:
var dificil_tasks = []; tasks.forEach (funcție (sarcină) if (task.duration> = 120) difficult_tasks.push (task););
Cu filtru
:
var difficult_tasks = tasks.filter (funcție (sarcină) return task.duration> = 120;); // Utilizarea ES6 var dificil_tasks = tasks.filter ((task) => task.duration> = 120);
Aici, am mers înainte și l-am lăsat afară index
și mulțime
argumente pentru apelul nostru, deoarece nu le folosim.
Ca și cum Hartă
, filtru
Ne lasă:
pentru fiecare
sau pentru
buclăApelul la care treci Hartă
trebuie să includă o declarație de returnare dacă doriți să funcționeze corect. Cu filtru
, trebuie să includeți și o declarație de returnare, și tu trebuie sa asigurați-vă că returnează o valoare booleană.
Dacă ați uitat declarația de returnare, apelul dvs. va reveni nedefinit
, care filtru
va constrânge necorespunzător fals
. În loc să arunce o eroare, va întoarce în tăcere o matrice goală!
Dacă mergeți pe celălalt traseu și returnați ceva care este nu este în mod explicit Adevărat
sau fals
, atunci filtru
va încerca să dau seama ce ați vrut să faceți prin aplicarea regulilor de constrângere ale JavaScript. De cele mai multe ori, acesta este un bug. Și, ca și cum ați uita declarația de returnare, va fi una tăcută.
Mereu asigurați-vă că apelurile dvs. includ o declarație explicită de returnare. Și mereu asigurați-vă că apelurile dvs. sunt înăuntru filtru
întoarcere Adevărat
sau fals
. Sanctitudinea voastră vă va mulțumi.
Încă o dată, cea mai bună modalitate de a înțelege o bucată de cod este ... să o scrieți. Să ne răsuciți ușor filtru
. Oamenii buni de la Mozilla au un polyfill de putere industrială pentru tine de citit.
var filter = funcție (matrice, apel invers) var filtered_array = []; array.forEach (funcție (element, index, array) if (apel invers (element, index, array)) filtered_array.push (element);); returnează filtered_array; ;
Hartă
creează o nouă matrice transformând fiecare element într-o matrice, în mod individual. filtru
creează o nouă matrice eliminând elemente care nu aparțin. reduce
, pe de altă parte, ia toate elementele într - o matrice și reduce într-o singură valoare.
Ca și cum Hartă
și filtru
, reduce
este definită pe Array.prototype
și astfel disponibilă pe orice matrice, iar dvs. primiți un apel invers ca prim argument. Dar, de asemenea, este nevoie de un al doilea argument opțional: valoarea pentru a începe combinarea tuturor elementelor dvs. de matrice în.
reduce
transmite apelul dvs. cu patru argumente:
reduce
peObservați că callback-ul primește a valoare anterioară pe fiecare iterație. La prima repetare, acolo este nici o valoare anterioară. De aceea aveți opțiunea de a trece reduce
o valoare inițială: acționează ca "valoarea anterioară" pentru prima iterație, atunci când altfel nu ar fi una.
În cele din urmă, rețineți asta reduce
returnează a valoare unică, nu o matrice care conține un singur element. Acest lucru este mai important decât s-ar părea și voi reveni la el în exemple.
reduce
in practicaDe cand reduce
este funcția pe care oamenii o găsesc cel mai străin de la început, vom începe prin mersul pas cu pas prin ceva simplu.
Să presupunem că vrem să găsim suma unei liste de numere. Folosind o buclă, care arată astfel:
var numere = [1, 2, 3, 4, 5], total = 0; numbers.forEach (funcție (număr) total + = număr;);
Deși acest lucru nu este un caz de utilizare rău pentru pentru fiecare
, reduce
are încă avantajul de a ne permite să evităm mutația. Cu reduce
, am scrie:
var total = [1, 2, 3, 4, 5] .reduceți (funcția (anterioară, actuală) returnați anterior + curent;, 0);
În primul rând, sunăm reduce
pe lista noastră de numerele.
Îi transmitem un apel invers, care acceptă valoarea anterioară și valoarea curentă ca argumente, și returnează rezultatul adăugării acestora. De când am trecut 0
ca un al doilea argument reduce
, se va folosi ca valoare de anterior
la prima iterație.
Dacă o luăm pas cu pas, se va arăta astfel:
Repetare | Anterior | Actual | Total |
---|---|---|---|
1 | 0 | 1 | 1 |
2 | 1 | 2 | 3 |
3 | 3 | 3 | 6 |
4 | 6 | 4 | 10 |
5 | 10 | 5 | 15 |
Dacă nu sunteți un fan al meselor, rulați acest fragment în consola:
var total = [1, 2, 3, 4, 5] .reduceți (funcția (precedent, curent, index) var val = precedent + curent; valoarea este "+ curent +", iar iterația curentă este "+ (index + 1)); retur val;, 0); console.log ("Bucla este terminată și valoarea finală este" + total + ".);
A recapitula: reduce
iterează peste toate elementele unei matrice, combinându-le cu toate acestea pe care le specificați în apelul dvs. de apel. La fiecare repetare, apelul dvs. are acces la anterior valoare, care este total de-așa-departe, sau acumulată; Valoarea curentă; indicele actual; și întreaga mulțime, dacă aveți nevoie de ele.
Să revenim la exemplul sarcinilor noastre. Am obținut o listă de nume de sarcini Hartă
, și o listă filtrată de sarcini care au durat mult timp cu ... bine, filtru
.
Dacă am vrea să cunoaștem timpul petrecut astăzi?
Folosind un pentru fiecare
bucla, ai scrie:
var total_time = 0; tasks.forEach (funcție (sarcină) // Semnul plus doar coerces // task.duration de la un String la un număr total_time + = (+ task.duration););
Cu reduce
, care devine:
var total_time = tasks.reduce (funcția (anterioară, curentă) returnați anterior + curent;, 0); // Utilizarea funcțiilor săgeți var total_time = tasks.reduce ((precedent, actual) precedent + curent);
Uşor.
Asta e aproape totul. Aproape, pentru că JavaScript ne oferă o metodă puțin cunoscută, numită reduceRight
. În exemplele de mai sus, reduce
a început la primul element din matrice, iterând de la stânga la dreapta:
var array_of_arrays = [[1, 2], [3, 4], [5, 6]]; var concatenated = array_of_arrays.reduce (funcția (precedentă, actuală) returnați anterior.concat (curent);); console.log (concatenate); // [1, 2, 3, 4, 5, 6];
reduceRight
face același lucru, dar în direcția opusă:
var array_of_arrays = [[1, 2], [3, 4], [5, 6]]; var concatenated = array_of_arrays.reduceRight (funcția (anterioară, actuală) returnă previous.concat (current);); console.log (concatenate); // [5, 6, 3, 4, 1, 2];
eu folosesc reduce
în fiecare zi, dar nu am nevoie niciodată reduceRight
. Cred, probabil, că nu o veți face. Dar în cazul în care faceți vreodată, acum știți că este acolo.
Cei trei mari gotchas cu reduce
sunteți:
întoarcere
reduce
returnează o singură valoareDin fericire, primele două sunt ușor de evitat. Deciderea a ceea ce ar trebui să fie valoarea inițială depinde de ceea ce faci, dar vei avea parte de el repede.
Ultimul ar părea un pic ciudat. Dacă reduce
doar întoarce vreodată o singură valoare, de ce te-ai aștepta la o matrice?
Există câteva motive bune pentru asta. Primul, reduce
întoarce întotdeauna un singur valoare, nu întotdeauna un singur număr. Dacă reduceți o matrice de matrice, de exemplu, va reveni la o singură matrice. Dacă vă aflați în obișnuință sau în reducerea matricelor, ar fi corect să vă așteptați ca o matrice care conține un singur element să nu fie un caz special.
În al doilea rând, dacă reduce
făcut întoarce un matrice cu o singură valoare, ar juca frumos frumos cu Hartă
și filtru
, și alte funcții pe tablourile pe care probabil că le folosiți.
Timpul pentru ultimul nostru aspect sub capota. Ca de obicei, Mozilla are un polyfill antiglont pentru reducere, dacă doriți să o verificați.
var reduce = funcția (matrice, apel invers, inițial) var acumulator = inițial || 0; array.forEach (funcție (element) acumulator = apel invers (acumulator, matrice [i]);); acumulator de retur; ;
Două lucruri de reținut, aici:
acumulator
in loc de anterior
. Aceasta este ceea ce veți vedea de obicei în sălbăticie.acumulator
o valoare inițială, dacă un utilizator oferă unul, și implicit la 0
, dacă nu. Așa este realul reduce
se comporta, de asemenea.În acest moment, s-ar putea să nu fiți acea impresionat.
Destul de corect: Hartă
, filtru
, și reduce
, pe cont propriu, nu sunt extrem de interesante.
La urma urmei, adevărata lor putere constă în capacitatea lor de a fi înlănțuite.
Să spunem că vreau să fac următoarele:
Mai întâi, să definim sarcinile noastre luni și marți:
var monday = ['nume': 'Scrie un tutorial', 'durata': 180, 'nume': 'Unele dezvoltări web', 'durata': 120]; var tuesday = ['nume': 'Păstrați scris acest tutorial', 'durata': 240, 'nume': ' lot de nimic "," durată ": 240]; var sarcinile = [luni, marți];
Și acum, transformarea noastră minunată:
var rezultat = tasks.reduce (functie (acumulator, curent) retur accumulator.concat (curent);) harta (function (task) return (task.duration / 60) return (= retur> = 2;) hartă (funcție (durată) durata returului * 25;)) reduce (funcția (acumulator, curent) return + (+ curent); hartă (funcție (dollar_amount) return '$' + dollar_amount.toFixed (2);)) reduce (funcția (formatted_dollar_amount) return formatted_dollar_amount;);
Sau, mai concis:
// concatenați matricea noastră 2D într-o singură listă var rezultat = tasks.reduce ((acc, current) => acc.concat (current)) // extrage durata sarcinii și convertește minutele la ore .map ((task) = > task.duration / 60) // Filtrați orice activitate care a durat mai puțin de două ore .filter ((duration) => duration> = 2) // Multiplicați durata fiecărei sarcini cu rata orară .map ((duration = > durata = 25) // Combinați sumele într-o singură sumă de dolar. reduce ((acc, current) => [(+ acc) + (+ curent)]) // Convertiți la o sumă dolar "destul de tipărită". map ((sumă) => '$' + amount.toFixed (2)) // Trageți singurul element al matricei de pe hartă .reduce ((formatted_amount) => formatted_amount);
Dacă ați reușit acest lucru, acest lucru ar trebui să fie destul de simplu. Există însă două fragmente de ciudățenie care trebuie explicate.
În primul rând, pe linia 10, trebuie să scriu:
// Remainder omit reduce (funcție (acumulator, curent) return [(+ acumulator) + (+ curent_];)
Două lucruri de explicat aici:
acumulator
și actual
constrânge valorile lor la numere. Dacă nu faceți acest lucru, valoarea returnată va fi șirul inutil, "12510075100"
.reduce
va scuipa o singură valoare, nu o matrice. Asta ar termina aruncarea a Eroare de scris
, pentru că puteți utiliza doar Hartă
pe o matrice! Cel de-al doilea bit care te-ar putea face un pic incomod este ultima reduce
, și anume:
// Remainder omit harta (functie (dollar_amount) return '$' + dollar_amount.toFixed (2);)) reduce (functie (formatted_dollar_amount) retur formatted_dollar_amount;);
Apelarea Hartă
returnează o matrice care conține o singură valoare. Aici sunăm reduce
pentru a scoate această valoare.
Celălalt mod de a face acest lucru ar fi să eliminați apelul de reducere și să indexați în matricea respectivă Hartă
scuipă:
var rezultat = tasks.reduce (functie (acumulator, curent) retur accumulator.concat (curent);) harta (function (task) return (task.duration / 60) return (= retur> = 2;) hartă (funcție (durată) durata returului * 25;)) reduce (funcția (acumulator, curent) return + (+ curent); hartă (funcție (dollar_amount) return '$' + dollar_amount.toFixed (2);) [0];
Este perfect corect. Dacă sunteți mai confortabil folosind un index de matrice, mergeți înainte.
Dar vă încurajez să nu faceți asta. Una dintre cele mai puternice căi de a utiliza aceste funcții este în domeniul programării reactive, unde nu veți fi liberi să folosiți indicii matrice. Impunând acest obicei, tehnicile reactive de învățare vor fi mult mai ușoare pe linie.
În cele din urmă, să vedem cum prietenul nostru pentru fiecare
buclă ar fi făcut-o:
var concatenated = monday.concat (marți), taxe = [], formatted_sum, hourly_rate = 25, total_fee = 0; concatenated.forEach (funcție (sarcină) var duration = task.duration / 60; dacă (duration> = 2) fees.push (duration * hourly_rate);); taxe pentru fiecare (funcție (comision) total_fee + = taxă); var formatted_sum = '$' + total_fee.toFixed (2);
Tolerabil, dar zgomotos.
În acest tutorial, ați învățat cum Hartă
, filtru
, și reduce
muncă; cum să le folosiți; și modul în care sunt implementate. Ați văzut că toți vă permit să evitați să mutați statul, pe care îl utilizați pentru
și pentru fiecare
buclele necesită și acum ar trebui să aveți o idee bună despre cum să le lanseze împreună.
Până acum, sunt sigur că ești dornic să practice și să citești în continuare. Iată primele trei sugestii pentru locația următoare:
JavaScript a devenit una dintre limbile de-facto de lucru pe web. Nu este fără curbele sale de învățare, și există o mulțime de cadre și biblioteci pentru a vă menține ocupați. Dacă sunteți în căutarea unor resurse suplimentare pentru a studia sau a utiliza în munca dvs., verificați ce avem la dispoziție pe piața Envato.
Dacă doriți mai mult pentru astfel de lucruri, verificați din când în când profilul meu; prinde-mă pe Twitter (@PelekeS); sau mi-a lovit blogul la http://peleke.me.
Întrebări, comentarii sau confuzii? Lăsați-le mai jos și voi face tot posibilul pentru a reveni la fiecare în parte.
Aflați JavaScript: Ghidul complet
Am creat un ghid complet care vă ajută să învățați JavaScript, indiferent dacă începeți doar ca dezvoltator web sau doriți să explorați subiecte mai avansate.