Ce nu v-au spus despre extrasurile ES5

Fiecare nouă versiune de JavaScript adaugă câteva bonusuri suplimentare care facilitează programarea. EcmaScript 5 a adăugat câteva metode necesare mulțime tip de date și, în timp ce găsiți resurse care vă învață cum să utilizați aceste metode, de obicei omite o discuție despre utilizarea acestora cu orice altceva decât o funcție plictisitoare personalizată.

Toate elementele suplimentare sunt ignorate găuri în tablouri.

Noile metode de array adăugate în ES5 sunt denumite de obicei Array Extras. Acestea ușurează procesul de lucru cu matricele, oferind metode pentru a efectua operații comune. Iată o listă aproape completă a noilor metode:

  • Array.prototype.map
  • Array.prototype.reduce
  • Array.prototype.reduceRight
  • Array.prototype.filter
  • Array.prototype.forEach
  • Array.prototype.every
  • Array.prototype.some

Array.prototype.indexOf și Array.prototype.lastIndexOf sunt, de asemenea, parte din această listă, dar acest tutorial va discuta doar cele șapte metode de mai sus.


Ce au spus ei

Aceste metode sunt destul de simple de utilizat. Ele execută o funcție pe care o furnizați ca primul lor argument, pentru fiecare element din matrice. În mod obișnuit, funcția furnizată ar trebui să aibă trei parametri: elementul, indexul elementului și întreaga matrice. Iată câteva exemple:

[1, 2, 3] .map (funcție (elem, index, arr) return elem * elem;); // returneaza [1, 4, 9] [1, 2, 3, 4, 5] .filter (functie (elem, index, arr) return elem% 2 === 0; // returneaza [2, 4] [1, 2, 3, 4, 5] .some (functie (elem, index, arr) return elem> = 3;); // returneaza adevaratele [1, 2, 3, 4, 5]. tot (functie (elem, index, arr) return elem> = 3;); // returnează false

reduce și reduceRight metode au o listă de parametri diferite. După cum sugerează numele lor, ele reduc o matrice la o singură valoare. Valoarea inițială a rezultatului este implicită la primul element din matrice, dar puteți trece un al doilea argument la aceste metode pentru a servi ca valoare inițială.

Funcția de apel invers pentru aceste metode acceptă patru argumente. Starea curentă este primul argument, iar argumentele rămase sunt elementul, indexul și matricea. Următoarele fragmente demonstrează utilizarea acestor două metode:

[1, 2, 3, 4, 5] .reduce (funcția (sumă, elem, index, arr) sumă return + elem;); // returneaza [1, 2, 3, 4, 5] .reduce (functie (suma, elem, index, arr) suma return + elem;, 10); // returnează 25

Dar probabil că ați știut deja toate astea, nu-i așa? Deci, să ne îndreptăm spre ceva cu care nu vă puteți cunoaște.


Programare funcțională la salvare

Este surprinzător faptul că mai mulți oameni nu știu acest lucru: nu trebuie să creați o funcție nouă și să o transmiteți .Hartă() Si prieteni. Chiar mai bine, puteți trece funcții încorporate, cum ar fi parseFloat cu nici un ambalaj necesar!

["1", "2", "3", "4"] hartă (parseFloat); // returnează [1, 2, 3, 4]

Rețineți că unele funcții nu vor funcționa conform așteptărilor. De exemplu, parseInt acceptă un radix ca al doilea argument. Acum rețineți că indexul elementului este transferat funcției ca al doilea argument. Deci, ce se va întoarce după cum urmează?

["1", "2", "3", "4"] hartă (parseInt);

Exact: [1, NaN, NaN, NaN]. Ca o explicație: baza 0 este ignorată; astfel încât prima valoare este analizată așa cum era de așteptat. Următoarele baze nu includ numărul trecut ca primul argument (de exemplu, baza 2 nu include 3), ceea ce duce la NaNs. Asigurați-vă că verificați în prealabil Mozilla Developer Network înainte de a utiliza o funcție și veți fi bine să plecați.

Pro-sfat: Puteți folosi chiar și constructorii construiți ca argumente, deoarece acestea nu trebuie să fie solicitate nou. Ca rezultat, o conversie simplă la o valoare booleană poate fi făcută folosind boolean, asa:

["da", 0, "nu", "", "adevărat", "fals"]. // returnează ["da", "nu", "adevărat", "fals"]

Sunt și alte câteva funcții frumoase encodeURIComponent, Date.parse (rețineți că nu puteți utiliza Data constructor, deoarece întoarce întotdeauna data curentă atunci când este apelat fără nou), Array.isArray și JSON.parse.


Nu uita să .aplica()

În timp ce utilizați funcțiile încorporate ca argumente pentru metodele de array care pot genera o sintaxă frumoasă, ar trebui să rețineți că puteți trece un matrice ca al doilea argument al Function.prototype.apply. Acest lucru este la îndemână, atunci când apelați metode, cum ar fi Math.max sau String.fromCharCode. Ambele funcții acceptă un număr variabil de argumente, deci va trebui să le înfășurați într-o funcție atunci când folosiți extrasele de array. Deci, în loc de:

var arr = [1, 2, 4, 5, 3]; var max = arr.reduce (funcția (a, b) retur Math.max (a, b););

Puteți scrie următoarele:

var arr = [1, 2, 4, 5, 3]; var max = Math.max.apply (null, arr);

Acest cod vine, de asemenea, cu un avantaj plăcut de performanță. Ca o notă laterală: în EcmaScript 6, veți putea să scrieți pur și simplu:

var arr = [1, 2, 4, 5, 3]; var max = Math.max (... arr); // Aceasta nu functioneaza in prezent!

Nenumărate Arrays

Toate elementele suplimentare sunt ignorate găuri în tablouri. Un exemplu:

var a = ["salut",,, "lume"]; // a [1] la a [4] nu sunt definite var count = a.reduce (funcția (count) retur count + 1;, 0); console.log (număr); // 2

Acest comportament are probabil un beneficiu de performanță, dar există cazuri când poate fi o adevărată durere în fund. Un astfel de exemplu ar putea fi atunci când aveți nevoie de o serie de numere aleatorii; nu este posibil să scrieți pur și simplu acest lucru:

var randomNums = array nou (5) .map (Math.random);

Dar amintiți-vă că puteți să-i numiți pe toți constructorii nativi fără nou. Și un alt lucru util: Function.prototype.apply nu ignoră găurile. Combinând acestea, acest cod returnează rezultatul corect:

var randomNums = Array.apply (null, array nou (5)) harta (Math.random);

Argumentul al doilea necunoscut

Cele mai multe dintre cele de mai sus sunt cunoscute și utilizate de mulți programatori în mod regulat. Ceea ce majoritatea nu știu (sau cel puțin nu o utilizează) este al doilea argument al majorității extraselor (numai reduce* funcțiile nu o suportă).

Cu ajutorul celui de-al doilea argument, puteți trece a acest valoarea funcției. Ca urmare, puteți utiliza prototip-metode. De exemplu, filtrarea unei matrice cu expresie obișnuită devine o linie liniară:

["foo", "bar", "baz"]. filtru (RegExp.prototype.test, / ^ b /); // returnează ["bar", "baz"]

De asemenea, verificarea dacă un obiect are anumite proprietăți devine cinch:

["foo", "isArray", "crea"]. unele (Object.prototype.hasOwnProperty, Object); // returneaza true (din cauza Object.create)

În cele din urmă, puteți utiliza toate metodele pe care doriți:

// permite sa faci ceva nebun [function (a) return a * a; , funcția (b) retur b * b * b; ] .map (Array.prototype.map, [1, 2, 3]); // returnează [[1, 4, 9], [1, 8, 27]]

Acest lucru devine nebun în momentul utilizării Function.prototype.call. Uita-te la asta:

["foo", "\ n \ tbar", "\ r \ nbaz \ t"] .map (funcția.prototype.call, String.prototype.trim); // returneaza ["foo", "bar", "baz"] [true, 0, null, []] harta (Function.prototype.call, Object.prototype.toString); // returnează ["[obiect Boolean]", "[obiect Număr]", "[obiect Nul]", "[obiect Array]

Desigur, pentru a vă place pe geek-ul dvs. interior, puteți, de asemenea, să utilizați Function.prototype.call ca al doilea parametru. Atunci când face acest lucru, fiecare element al matricei este numit cu indexul său ca primul argument și întreaga matrice ca a doua:

[function (index, arr) // indiferent de ce ai vrea sa faci cu ea] forEach (Function.prototype.call, Function.prototype.call);

Să construim ceva util

Cu toate acestea, să construim un simplu calculator. Vrem doar să sprijinim operatorii de bază (+, -, *, /), și trebuie să respectăm procedura operatorului. Deci, multiplicarea (*) și diviziune (/) trebuie evaluate înainte de adăugare (+) și scăderea (-).

În primul rând, definim o funcție care acceptă un șir reprezentând calculul ca primul și singurul argument.

calculul funcției (calcul) 

În corpul funcției, începem să convertim calculul într-o matrice utilizând o expresie regulată. Apoi, ne asigurăm că am analizat întregul calcul prin adăugarea pieselor folosind Array.prototype.join și comparând rezultatul cu calculul inițial.

var parts = calcul.match (// cifre | operatori | spații albe /(?:\-?[\d\.]+)|[-\+\\\/]|\s+/g); dacă (calcul! == parts.join ("")) arunca o eroare nouă ("calculul nu a putut fi analizat")

După aceea, sunăm String.prototype.trim pentru fiecare element pentru a elimina spațiul alb. Apoi, filtram matricea și eliminăm elementele false (adică: f șiruri goale).

părți = parts.map (Funcție.prototype.call, String.prototype.trim); părți = partss.filter (Boolean);

Acum, construim o matrice separată care conține numere parsate.

var nums = parte.map (parseFloat);

Aveți posibilitatea să treceți funcții încorporate, cum ar fi parseFloat cu nici un ambalaj necesar!

În acest moment, cel mai simplu mod de a continua este simplu pentru-buclă. În cadrul acestuia, construim o altă matrice (numită prelucrate) cu multiplicare și diviziune deja aplicate. Ideea de bază este să reducem fiecare operațiune la o adăugare, astfel încât ultimul pas să devină destul de banal.

Verificăm fiecare element din Nums pentru a vă asigura că nu este NaN; dacă nu este un număr, atunci este un operator. Cel mai simplu mod de a face acest lucru este profitând de faptul că, în JavaScript, NaN! == NaN. Când găsim un număr, îl adăugăm în matricea rezultatelor. Când găsim un operator, îl aplicăm. Am sări peste operațiunile adiționale și schimbăm numai semnul următorului număr pentru scădere.

Înmulțirea și împărțirea trebuie calculate folosind cele două numere din jur. Deoarece am adăugat deja numărul anterior la matrice, trebuie să îl eliminăm folosind Array.prototype.pop. Rezultatul calculului se adaugă la matricea de rezultate, gata să fie adăugată.

var prelucrat = []; pentru (var i = 0; i < parts.length; i++) if( nums[i] === nums[i] ) processed.push( nums[i] );  else  switch( parts[i] )  case "+": continue; //ignore case "-": processed.push(nums[++i] * -1); break; case "*": processed.push(processed.pop() * nums[++i]); break; case "/": processed.push(processed.pop() / nums[++i]); break; default: throw new Error("unknown operation: " + parts[i]);   

Ultimul pas este destul de ușor: tocmai adăugăm toate numerele și ne întoarcem rezultatul final.

returnează processed.reduce (funcție (rezultat, elem) rezultatul returnului + elem;);

Funcția completă ar trebui să arate astfel:

Calculați calculul (calculați) // construiți un matrice care conține părțile individuale var parts = calcul.match (// digiți | operatori | spațiu alb / (?: \ -? [\ d \.] +) * \ /] | \ s + / g); // test daca totul a fost egalat daca (calcul! == parts.join ("")) arunca o noua eroare ("nu a putut calcula calculul") // elimina toate partile din spatiul alb = parts.map (Function.prototype. apel, String.prototype.trim); părți = partss.filter (Boolean); // construiește o matrice separată care conține numere parsate var nums = parts.map (parseFloat); // construi o altă matrice cu toate operațiile reduse la adiții var processed = []; pentru (var i = 0; i < parts.length; i++) if( nums[i] === nums[i] ) //nums[i] isn't NaN processed.push( nums[i] );  else  switch( parts[i] )  case "+": continue; //ignore case "-": processed.push(nums[++i] * -1); break; case "*": processed.push(processed.pop() * nums[++i]); break; case "/": processed.push(processed.pop() / nums[++i]); break; default: throw new Error("unknown operation: " + parts[i]);    //add all numbers and return the result return processed.reduce(function(result, elem) return result + elem; ); 

Bine, deci să-l testăm:

calculează ("2 + 2,5 * 2") // returnează 7 calculează ("12/6 + 4 * 3") // returnează 14

Se pare că funcționează! Există încă unele cazuri de margine care nu sunt tratate, cum ar fi calculele primului operator sau numerele care conțin mai multe puncte. Sprijinul pentru paranteze ar fi frumos, dar nu ne vom îngrijora să detaliem mai multe detalii în acest exemplu simplu.


Înfășurarea în sus

În timp ce extrasurile ES5 ar putea, la început, să pară destul de banale, ele dezvăluie destul de mult adâncime, odată ce le oferiți o șansă. Dintr-o dată, programarea funcțională în JavaScript devine mai mult decât iadul de apel callback și codul de spaghetti. Realizând acest lucru a fost un adevărat ochi-deschizător pentru mine și a influențat modul meu de a scrie programe.

Desigur, așa cum am văzut mai sus, există întotdeauna cazuri în care doriți să utilizați o buclă obișnuită. Dar, și asta e partea frumoasă, nu trebuie.

Cod