Wat zij u niet hebben verteld over de Array-extra's van ES5

Elke nieuwe versie van JavaScript voegt extra goodies toe die het programmeren eenvoudiger maken. EcmaScript 5 heeft enkele veelgevraagde methoden toegevoegd aan de reeks gegevenstype, en hoewel u bronnen kunt vinden die u leren hoe deze methoden te gebruiken, laten ze meestal een discussie over het gebruik ervan met iets anders dan een saaie, aangepaste functie achterwege.

Alle array-extra's worden genegeerd gaten in arrays.

De nieuwe matrixmethoden die in ES5 zijn toegevoegd, worden meestal aangeduid als Array-extra's. Ze verlichten het proces van het werken met arrays door methoden aan te bieden voor het uitvoeren van algemene bewerkingen. Hier is een bijna volledige lijst van de nieuwe methoden:

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

Array.prototype.indexOf en Array.prototype.lastIndexOf maken ook deel uit van die lijst, maar deze tutorial zal alleen de bovenstaande zeven methoden bespreken.


Wat ze je hebben verteld

Deze methoden zijn vrij eenvoudig te gebruiken. Ze voeren een functie uit die u als hun eerste argument levert, voor elk element in de array. Normaal gesproken zou de geleverde functie drie parameters moeten hebben: het element, de index van het element en de hele array. Hier zijn een paar voorbeelden:

[1, 2, 3] .map (functie (elem, index, arr) return elem * elem;); // geeft [1, 4, 9] [1, 2, 3, 4, 5] terug. filter (function (elem, index, arr) return elem% 2 === 0;); // geeft [2, 4] [1, 2, 3, 4, 5] terug. .sommige (functie (elem, index, arr) return elem> = 3;); // geeft true [1, 2, 3, 4, 5]. everyy (function (elem, index, arr) return elem> = 3;); // geeft false als resultaat

De verminderen en reduceRight methoden hebben een andere parameterlijst. Zoals hun namen suggereren, verkleinen ze een array tot een enkele waarde. De beginwaarde van het resultaat is standaard ingesteld op het eerste element in de array, maar u kunt een tweede argument doorgeven aan deze methoden om als de beginwaarde te dienen.

De callback-functie voor deze methoden accepteert vier argumenten. De huidige status is het eerste argument en de resterende argumenten zijn het element, de index en de array. De volgende fragmenten demonstreren het gebruik van deze twee methoden:

[1, 2, 3, 4, 5] .reduce (functie (som, elem, index, arr) return sum + elem;); // geeft 15 [1, 2, 3, 4, 5] .reduce (functie (som, elem, index, arr) return sum + elem;, 10); // geeft 25 als resultaat

Maar waarschijnlijk wist je dit alles al, toch? Laten we dus verdergaan naar iets waar u misschien nog niet bekend mee bent.


Functioneel programmeren voor de redding

Het is verrassend dat meer mensen dit niet weten: je hoeft geen nieuwe functie aan te maken en door te geven .kaart() en vrienden. Nog beter, je kunt ingebouwde functies doorgeven, zoals parseFloat zonder omhulsel vereist!

["1", "2", "3", "4"]. Map (parseFloat); // geeft [1, 2, 3, 4] als resultaat

Merk op dat sommige functies niet werken zoals verwacht. Bijvoorbeeld, parseInt accepteert een radix als een tweede argument. Onthoud nu dat de index van het element als een tweede argument aan de functie wordt doorgegeven. Dus wat zal het volgende teruggeven?

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

Precies: [1, NaN, NaN, NaN]. Ter verduidelijking: base 0 wordt genegeerd; dus de eerste waarde wordt zoals verwacht geparseerd. De volgende basen bevatten niet het aantal doorgegeven als het eerste argument (bijvoorbeeld base 2 omvat geen 3), wat leidt tot NaNs. Zorg er dus voor dat je het Mozilla Developer Network vooraf controleert voordat je een functie gebruikt en je bent klaar om te gaan.

Pro-tip: U kunt ingebouwde constructors zelfs als argumenten gebruiken, omdat ze niet hoeven te worden aangeroepen nieuwe. Dientengevolge kan een eenvoudige conversie naar een Booleaanse waarde worden gedaan met behulp van Boolean, zoals dit:

["yes", 0, "no", "", "true", "false"]. filter (Boolean); // retourneert ["yes", "no", "true", "false"]

Een paar andere leuke functies zijn encodeURIComponent, Date.parse (merk op dat u de. niet kunt gebruiken Datum constructor omdat het altijd de huidige datum retourneert wanneer er zonder wordt gebeld nieuwe), Array.isArray en JSON.parse.


Vergeet niet om .van toepassing zijn()

Hoewel het gebruik van ingebouwde functies als argumenten voor matrixmethoden een mooie syntaxis kan opleveren, moet u ook onthouden dat u een array kunt doorgeven als het tweede argument van Function.prototype.apply. Dit is handig, bij het aanroepen van methoden, zoals Math.max of String.fromCharCode. Beide functies accepteren een variabel aantal argumenten, dus u moet ze omslaan in een functie wanneer u de array-extra's gebruikt. Dus in plaats van:

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

U kunt het volgende schrijven:

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

Deze code heeft ook een mooie prestatie-uitkering. Als een kanttekening: in EcmaScript 6 kun je gewoon schrijven:

var arr = [1, 2, 4, 5, 3]; var max = Math.max (... arr); // DIT WERKT OP DAT MOMENT NIET!

Hole-less-arrays

Alle array-extra's worden genegeerd gaten in arrays. Een voorbeeld:

var a = ["hallo",,,,, "wereld"]; // a [1] tot a [4] zijn niet gedefinieerd var count = a.reduce (function (count) return count + 1;, 0); console.log (count); // 2

Dit gedrag komt waarschijnlijk met een prestatievoordeel, maar er zijn gevallen waarin het een echte pijn in de kont kan zijn. Een voorbeeld hiervan is wanneer u een array van willekeurige getallen nodig hebt; het is niet mogelijk om gewoon dit te schrijven:

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

Maar vergeet niet dat je alle native constructors zonder kunt bellen nieuwe. En nog een handige lekkernij: Function.prototype.apply negeert geen gaten. Door deze te combineren, geeft deze code het correcte resultaat terug:

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

Het onbekende tweede argument

Het meeste van het bovenstaande is bekend en wordt door veel programmeurs regelmatig gebruikt. Wat de meesten van hen niet weten (of in ieder geval niet gebruiken) is het tweede argument van de meeste array-extra's (alleen de verminderen* functies ondersteunen het niet).

Met behulp van het tweede argument, kunt u slagen voor een deze waarde voor de functie. Als gevolg hiervan kunt u gebruiken prototype-methoden. Het filteren van een array met een reguliere expressie wordt bijvoorbeeld een one-liner:

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

Ook het controleren of een object bepaalde eigenschappen heeft, wordt een makkie:

["foo", "isArray", "create"]. some (Object.prototype.hasOwnProperty, Object); // geeft true terug (vanwege Object.create)

Uiteindelijk kunt u elke methode gebruiken die u wilt:

// laat iets gek doen [functie (a) return a * a; , functie (b) return b * b * b; ] .map (Array.prototype.map, [1, 2, 3]); // geeft [[1, 4, 9], [1, 8, 27]] terug

Dit wordt krankzinnig tijdens het gebruik Function.prototype.call. Kijk dit:

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

Natuurlijk, om je innerlijke nerd te behagen, kun je ook gebruiken Function.prototype.call als de tweede parameter. Hierbij wordt elk element van de array aangeroepen met zijn index als het eerste argument en de hele array als de tweede:

[functie (index, arr) // wat je er ook mee zou willen doen)]. voor elk (Function.prototype.call, Function.prototype.call);

Laten we iets nuttigs bouwen

Laten we met al dat gezegd, een eenvoudige rekenmachine bouwen. We willen alleen de basisoperatoren ondersteunen (+, -, *, /), en we moeten de procedure van de exploitant respecteren. Dus vermenigvuldiging (*) en divisie (/) moeten vóór de toevoeging worden geëvalueerd (+) en aftrekking (-).

Allereerst definiëren we een functie die een tekenreeks accepteert die de berekening als het eerste en enige argument vertegenwoordigt.

functie berekenen (berekening) 

In de hoofdtekst van de functie beginnen we de berekening om te zetten in een array met behulp van een reguliere expressie. Vervolgens zorgen we ervoor dat we de hele berekening hebben geparseerd door de onderdelen te combineren met Array.prototype.join en het vergelijken van het resultaat met de oorspronkelijke berekening.

var parts = calculation.match (// digits | operators | whitespace /(?:\-?[\d\.]+)|[-\+\*\/]|\s+/g); if (calculation! == parts.join ("")) gooi nieuwe fout ("kon berekening niet parseren")

Daarna bellen we String.prototype.trim voor elk element om witruimte te elimineren. Vervolgens filteren we de array en verwijderen we falsey-elementen (dwz: f lege tekenreeksen).

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

Nu bouwen we een aparte array die geparseerde getallen bevat.

var nums = parts.map (parseFloat);

U kunt ingebouwde functies doorgeven, zoals parseFloat zonder omhulsel vereist!

Op dit punt is de eenvoudigste manier om door te gaan eenvoudig voor-lus. Daarin bouwen we een andere array (genaamd verwerkte) met vermenigvuldiging en deling al toegepast. Het basisidee is om elke bewerking terug te brengen tot een toevoeging, zodat de laatste stap vrij triviaal wordt.

We controleren elk element van de nums array om te controleren of dit niet zo is NaN; als het geen nummer is, dan is het een operator. De gemakkelijkste manier om dit te doen is door gebruik te maken van het feit dat, in JavaScript, NaN! == NaN. Wanneer we een getal vinden, voegen we het toe aan de resultaatarray. Wanneer we een operator vinden, passen we die toe. We slaan toevoegbewerkingen over en veranderen alleen het teken van het volgende getal voor aftrekken.

Vermenigvuldiging en deling moeten worden berekend met behulp van de twee omliggende getallen. Omdat we het vorige nummer al aan de array hebben toegevoegd, moet het worden verwijderd met Array.prototype.pop. Het resultaat van de berekening wordt toegevoegd aan de resultaatarray, klaar om te worden toegevoegd.

var verwerkt = []; 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]);   

De laatste stap is vrij eenvoudig: we voegen gewoon alle cijfers toe en retourneren ons eindresultaat.

return processed.reduce (functie (resultaat, elem) retourresultaat + elem;);

De voltooide functie zou er zo uit moeten zien:

functie berekenen (berekening) // bouw een array met de afzonderlijke delen var parts = calculation.match (// digits | operators | whitespace / (?: \ -? [\ d \.] +) | [- \ + \ * \ /] | \ s + / g); // test of alles overeenkwam met (calculation! == parts.join ("")) gooi nieuwe fout ("kon berekening niet parseren") // verwijder alle witruimte-onderdelen = parts.map (Function.prototype. call, String.prototype.trim); parts = parts.filter (Boolean); // bouw een aparte array met geparseerde getallen var nums = parts.map (parseFloat); // bouw een andere array met alle bewerkingen teruggebracht tot toevoegingen var verwerkt = []; 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é, laten we het testen:

berekenen ("2 + 2.5 * 2") // geeft 7 als resultaat ("12/6 + 4 * 3") // geeft 14 als resultaat

Het lijkt te werken! Er zijn nog steeds enkele randgevallen die niet worden afgehandeld, zoals operator-eerste berekeningen of nummers die meerdere punten bevatten. Ondersteuning voor haakjes zou leuk zijn, maar we zullen ons geen zorgen maken over meer details in dit eenvoudige voorbeeld.


Afsluiten

Hoewel ES5-array-extra's in eerste instantie tamelijk triviaal lijken, onthullen ze nogal wat diepte, als je ze eenmaal een kans geeft. Plots wordt functioneel programmeren in JavaScript meer dan callback-hel en spaghetti-code. Dit realiseren was een echte eye-opener voor mij en beïnvloedde mijn manier van schrijven van programma's.

Natuurlijk, zoals hierboven te zien, zijn er altijd gevallen waarin je in plaats daarvan een gewone lus wilt gebruiken. Maar dat is het leuke deel, dat hoeft niet.