Hva de ikke fortalte deg om ES5's Array Extras

Hver ny versjon av JavaScript legger til noen ekstra godbiter som gjør programmeringen enklere. EcmaScript 5 la til noen mye trengte metoder til Array datatype, og mens du kan finne ressurser som lærer deg hvordan du bruker disse metodene, utelater de vanligvis en diskusjon om bruk av dem med noe annet enn en kjedelig, tilpasset funksjon.

Alle array extras ignorerer hull i arrays.

De nye array-metodene som er lagt til i ES5, kalles vanligvis Array Extras. De lette prosessen med å jobbe med arrays ved å gi metoder for å utføre vanlige operasjoner. Her er en nesten fullstendig liste over de nye metodene:

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

Array.prototype.indexOf og Array.prototype.lastIndexOf er også en del av listen, men denne opplæringen vil bare diskutere de ovennevnte syv metodene.


Hva de fortalt deg

Disse metodene er ganske enkle å bruke. De utfører en funksjon som du oppgir som deres første argument, for hvert element i gruppen. Vanligvis bør den medfølgende funksjonen ha tre parametre: elementet, elementets indeks og hele gruppen. Her er noen eksempler:

[1, 2, 3] .map (funksjon (elem, indeks, arr) return elem * elem;); // returnerer [1, 4, 9] [1, 2, 3, 4, 5] .filter (funksjon (elem, indeks, arr) return elem% 2 === 0;); // returnerer [2, 4] [1, 2, 3, 4, 5] .some (funksjon (elem, indeks, arr) return elem> = 3;); // returnerer sant [1, 2, 3, 4, 5] .every (funksjon (elem, indeks, arr) return elem> = 3;); // returnerer false

De redusere og reduceRight metodene har en annen parameterliste. Som navnene deres antyder, reduserer de et array til en enkelt verdi. Den opprinnelige verdien av resultatet er standard til det første elementet i matrisen, men du kan passere et andre argument til disse metodene for å tjene som startverdi.

Tilbakeringingsfunksjonen for disse metodene godtar fire argumenter. Nåværende tilstand er det første argumentet, og de gjenværende argumentene er elementet, indeksen og arrayen. Følgende utdrag viser bruk av disse to metodene:

[1, 2, 3, 4, 5] .reduce (funksjon (sum, elem, indeks, arr) retur sum + elem;); // returnerer 15 [1, 2, 3, 4, 5] .reduce (funksjon (sum, elem, indeks, arr) retur sum + elem;, 10); // returnerer 25

Men du visste sikkert sikkert alt dette, ikke sant? Så la oss flytte inn på noe du kanskje ikke er kjent med.


Funksjonell programmering til redning

Det er overraskende at flere ikke vet dette: Du trenger ikke å opprette en ny funksjon og sende den til .kart() og venner. Enda bedre, du kan passere innebygde funksjoner, for eksempel parseFloat uten innpakning kreves!

["1", "2", "3", "4"]. Kart (parseFloat); // returnerer [1, 2, 3, 4]

Vær oppmerksom på at enkelte funksjoner ikke fungerer som forventet. For eksempel, parseInt aksepterer en radix som et annet argument. Husk nå at elementets indeks er overført til funksjonen som et annet argument. Så hva kommer følgende tilbake?

["1", "2", "3", "4"]. Kart (parseInt);

Nøyaktig: [1, NaN, NaN, NaN]. Som en forklaring ignoreres base 0; så blir den første verdien analysert som forventet. Følgende baser inkluderer ikke nummeret som er bestått som det første argumentet (f.eks. Base 2 inkluderer ikke 3), som fører til NaNs. Så sørg for å sjekke Mozilla Developer Network på forhånd før du bruker en funksjon, og du vil være god å gå.

Pro-tips: Du kan til og med bruke innebygde konstruktører som argumenter, da de ikke kreves å bli kalt med ny. Som et resultat kan en enkel konvertering til en boolsk verdi gjøres ved å bruke boolean, som dette:

["ja", 0, "nei", "", "ekte", "falskt"). filter (boolsk); // returnerer ["ja", "nei", "ekte", "falskt"]

Et par andre fine funksjoner er encodeURIComponent, Date.parse (merk at du ikke kan bruke Dato Konstruktør da det alltid returnerer gjeldende dato når det kalles uten ny), Array.isArray og JSON.parse.


Ikke glem å .søke om()

Når du bruker innebygde funksjoner som argumenter for matrise-metoder, kan det være en fin syntaks, bør du også huske at du kan passere en matrise som det andre argumentet om Function.prototype.apply. Dette er praktisk når du ringer metoder, som Math.max eller String.fromCharCode. Begge funksjonene godtar et variabelt antall argumenter, så du må pakke dem inn i en funksjon når du bruker array-tilleggene. Så i stedet for:

var ar = [1, 2, 4, 5, 3]; var max = arr.reduce (funksjon (a, b) return Math.max (a, b););

Du kan skrive følgende:

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

Denne koden kommer også med en god ytelsesfordel. Som et sideblad: I EcmaScript 6 kan du bare skrive:

var ar = [1, 2, 4, 5, 3]; var max = Math.max (... arr); // DETTE BRUKER IKKE VIRKSOMHET!

Hole-less Arrays

Alle array extras ignorerer hull i arrays. Et eksempel:

var a = ["hei",,,,, "verden"]; // a [1] til en [4] er ikke definert var telle = a.reduce (funksjon (telle) returtall + 1;, 0); console.log (count); // 2

Denne oppførselen kommer sannsynligvis med en ytelsesfordel, men det er tilfeller når det kan være en ekte smerte i rumpa. Et slikt eksempel kan være når du trenger en rekke tilfeldige tall; det er ikke mulig å bare skrive dette:

var randomNums = new Array (5) .map (Math.random);

Men husk at du kan ringe alle innbyggere uten ny. Og en annen nyttig tidbit: Function.prototype.apply ignorerer ikke hull. Kombinere disse, returnerer denne koden det riktige resultatet:

var randomNums = Array.apply (null, ny Array (5)). kart (Math.random);

Det ukjente andre argumentet

De fleste av de ovennevnte er kjent og brukt av mange programmerere med jevne mellomrom. Det de fleste av dem ikke kjenner (eller i det minste ikke bruker) er det andre argumentet for de fleste av ekstramaterialene (bare redusere* funksjoner støtter ikke det).

Ved å bruke det andre argumentet, kan du passere en dette verdi til funksjonen. Som et resultat kan du bruke prototype-metoder. For eksempel blir filtrering av en matrise med et vanlig uttrykk en en-liner:

["foo", "bar", "baz"]. filter (RegExp.prototype.test, / ^ b /); // returnerer ["bar", "baz"]

Også å sjekke om et objekt har bestemte egenskaper blir en cinch:

["foo", "isArray", "create"]. noen (Object.prototype.hasOwnProperty, Object); // returnerer sant (på grunn av Object.create)

Til slutt kan du bruke alle metoder du vil:

// kan gjøre noe galt [funksjon (a) return a * a; , funksjon (b) retur b * b * b; ] .map (Array.prototype.map, [1, 2, 3]); // returnerer [[1, 4, 9], [1, 8, 27]]

Dette blir vanvittig når du bruker Function.prototype.call. Se dette:

["foo", "\ n \ tbar", "\ r \ nbaz \ t") .kart (Function.prototype.call, String.prototype.trim); // returnerer ["foo", "bar", "baz"] [sann, 0, null, []]. kart (Function.prototype.call, Object.prototype.toString); // returnerer ["[object Boolean]", "[object Number]", "[Object Null]", "[object Array]"

Selvfølgelig, for å tilfredsstille din indre geek, kan du også bruke Function.prototype.call som den andre parameteren. Når det gjøres, kalles alle elementene i arrayet med indeksen som det første argumentet og hele gruppen som den andre:

[funksjon (indeks, arr) // hva du kanskje vil gjøre med det]. forEach (Function.prototype.call, Function.prototype.call);

Lar oss bygge noe nyttig

Med alt sagt, la oss bygge en enkel kalkulator. Vi vil bare støtte de grunnleggende operatørene (+, -, *, /), og vi må respektere operatørprosedyren. Så, multiplikasjon (*) og divisjon (/) må vurderes før tilsetning (+) og subtraksjon (-).

For det første definerer vi en funksjon som aksepterer en streng som representerer beregningen som det første og eneste argumentet.

funksjon beregne (beregning) 

I funksjonsdelen begynner vi å konvertere beregningen til en tabell ved å bruke et regulært uttrykk. Da sikrer vi at vi analyserte hele beregningen ved å bli med på delene ved hjelp av Array.prototype.join og sammenligne resultatet med den opprinnelige beregningen.

var parts = calculation.match (// sifre | operatører | whitespace /(?:\-?[\d\.]+)|[-\+\*\/]|s +/g); hvis (beregning! == parts.join ("")) kaste ny feil ("kunne ikke analysere beregning")

Etter det kaller vi String.prototype.trim for hvert element for å eliminere hvitrom. Da filtrerer vi arrayet og fjerner falsey-elementer (dvs.: f tomme strenger).

deler = parts.map (Function.prototype.call, String.prototype.trim); deler = parts.filter (boolsk);

Nå bygger vi et eget utvalg som inneholder tolkede tall.

var nums = parts.map (parseFloat);

Du kan passere innebygde funksjoner som parseFloat uten innpakning kreves!

På dette punktet er den enkleste måten å fortsette, en enkel til-sløyfe. Innenfor det bygger vi en annen matrise (oppkalt Bearbeidet) med multiplikasjon og divisjon allerede brukt. Den grunnleggende ideen er å redusere hver operasjon til et tillegg, slik at det siste trinnet blir ganske trivielt.

Vi sjekker hvert element av nums array for å sikre at det ikke er det NaN; hvis det ikke er et nummer, så er det en operatør. Den enkleste måten å gjøre dette på er å utnytte det faktum at i JavaScript, NaN! == NaN. Når vi finner et tall, legger vi det til resultatlisten. Når vi finner en operatør, bruker vi den. Vi hopper over tilleggsoperasjoner og endrer bare tegnet på neste nummer for subtraksjon.

Multiplikasjon og divisjon må beregnes ved hjelp av de to omgivende tallene. Fordi vi allerede har vedlagt det forrige nummeret i arrayet, må det fjernes ved hjelp av Array.prototype.pop. Resultatet av beregningen blir lagt til resultatlisten, klar til å bli lagt til.

var prosessert = []; for (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]);   

Det siste trinnet er ganske enkelt: Vi legger bare til alle tall og returnerer vårt endelige resultat.

returner processed.reduce (funksjon (resultat, elem) returresultat + elem;);

Den ferdige funksjonen skal se slik ut:

funksjon beregne (beregning) // bygge en matrise som inneholder de enkelte delene var delene = calculation.match (// sifre | operatører | whitespace / (?: \ -? [\ d \.] +) | [- \ + \ * \ /] | \ s + / g); // test hvis alt var matchet hvis (beregning! == parts.join ("")) kaste ny feil ("kunne ikke analysere beregning") // fjerne alle hvite mellomrom = parts.map (Function.prototype. ring, String.prototype.trim); deler = parts.filter (boolsk); // bygge et eget utvalg som inneholder parserte tall var nums = parts.map (parseFloat); // bygge en annen matrise med alle operasjoner redusert til tillegg var processed = []; for (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; ); 

Ok, så la oss teste det:

beregne ("2 + 2,5 * 2") // returnerer 7 beregne ("12/6 + 4 * 3") // returnerer 14

Det ser ut til å fungere! Det er fortsatt noen kantsaker som ikke håndteres, for eksempel operatør-første beregninger eller tall som inneholder flere punkter. Støtte til parentes ville være hyggelig, men vi vil ikke bekymre deg for å grave inn i flere detaljer i dette enkle eksempelet.


Wrapping Up

Mens ES5s array-extras kanskje først synes å være ganske trivielle, avslører de ganske dyp, når du gir dem en sjanse. Plutselig blir funksjonell programmering i JavaScript mer enn tilbakekallingshelv og spagetti-kode. Å realisere dette var en ekte øyeåpner for meg og påvirket min måte å skrive programmer på.

Selvfølgelig, som vist ovenfor, er det alltid tilfeller der du vil bruke en vanlig sløyfe i stedet. Men, og det er den fine delen, du trenger ikke.