Cosa non ti hanno detto sugli extra di ES5

Ogni nuova versione di JavaScript aggiunge alcuni extra extra che semplificano la programmazione. EcmaScript 5 ha aggiunto alcuni metodi molto necessari per schieramento tipo di dati e, mentre è possibile trovare risorse che insegnano come utilizzare questi metodi, in genere omettono una discussione sull'utilizzo di essi con qualcosa di diverso da una noiosa funzione personalizzata.

Tutti gli extra dell'array ignorano fori negli array.

I nuovi metodi di array aggiunti in ES5 vengono generalmente indicati come Array Extras. Semplificano il processo di utilizzo degli array fornendo metodi per eseguire operazioni comuni. Ecco un elenco quasi completo dei nuovi metodi:

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

Array.prototype.indexOf e Array.prototype.lastIndexOf fanno parte di questa lista, ma questo tutorial discuterà solo i sette metodi sopra elencati.


Cosa ti hanno detto

Questi metodi sono abbastanza semplici da usare. Eseguono una funzione che fornisci come primo argomento, per ogni elemento dell'array. In genere, la funzione fornita dovrebbe avere tre parametri: l'elemento, l'indice dell'elemento e l'intero array. Ecco alcuni esempi:

[1, 2, 3] .map (function (elem, index, arr) return elem * elem;); // restituisce [1, 4, 9] [1, 2, 3, 4, 5] .filter (function (elem, index, arr) return elem% 2 === 0;); // restituisce [2, 4] [1, 2, 3, 4, 5] .some (funzione (elem, index, arr) return elem> = 3;); // restituisce true [1, 2, 3, 4, 5] .every (function (elem, index, arr) return elem> = 3;); // restituisce false

Il ridurre e reduceRight i metodi hanno una lista di parametri diversa. Come suggeriscono i loro nomi, riducono una matrice a un singolo valore. Il valore iniziale del risultato è il valore predefinito del primo elemento dell'array, ma è possibile passare un secondo argomento a questi metodi per fungere da valore iniziale.

La funzione di callback per questi metodi accetta quattro argomenti. Lo stato corrente è il primo argomento e gli argomenti rimanenti sono l'elemento, l'indice e l'array. I seguenti frammenti dimostrano l'utilizzo di questi due metodi:

[1, 2, 3, 4, 5] .reduce (funzione (somma, elem, indice, arr) return sum + elem;); // restituisce 15 [1, 2, 3, 4, 5] .reduce (funzione (somma, elem, indice, arr) return sum + elem;, 10); // restituisce 25

Ma probabilmente lo sapevi già, vero? Quindi passiamo a qualcosa che potrebbe non essere familiare.


Programmazione funzionale al salvataggio

È sorprendente che più persone non lo sappiano: non devi creare una nuova funzione e passarla a .carta geografica() e amici. Ancora meglio, è possibile passare funzioni integrate, come ad esempio parseFloat senza bisogno di wrapper!

["1", "2", "3", "4"]. Map (parseFloat); // restituisce [1, 2, 3, 4]

Si noti che alcune funzioni non funzioneranno come previsto. Per esempio, parseInt accetta un radix come secondo argomento. Ora ricorda che l'indice dell'elemento viene passato alla funzione come secondo argomento. Quindi, quale sarà il seguente ritorno?

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

Esattamente: [1, NaN, NaN, NaN]. Come spiegazione: la base 0 viene ignorata; quindi, il primo valore viene analizzato come previsto. Le seguenti basi non includono il numero passato come primo argomento (ad esempio la base 2 non include 3), che porta a NaNS. Assicurati quindi di controllare la Mozilla Developer Network in anticipo prima di utilizzare una funzione e sarai a posto.

Pro-tip: È possibile utilizzare anche costruttori built-in come argomenti, poiché non è necessario chiamarli con nuovo. Di conseguenza, è possibile eseguire una semplice conversione in un valore booleano booleano, come questo:

["sì", 0, "no", "", "true", "false"]. filter (Boolean); // restituisce ["sì", "no", "vero", "falso"]

Un paio di altre belle funzioni sono encodeURIComponent, Date.parse (nota che non puoi usare il Data costruttore in quanto restituisce sempre la data corrente quando chiamato senza nuovo), Array.isArray e JSON.parse.


Non dimenticare .applicare()

Anche se l'uso di funzioni built-in come argomenti per i metodi array può creare una bella sintassi, dovresti anche ricordare che puoi passare un array come secondo argomento di Function.prototype.apply. Questo è utile, quando si chiama metodi, come Math.max o String.fromCharCode. Entrambe le funzioni accettano un numero variabile di argomenti, quindi è necessario avvolgerli in una funzione quando si utilizzano gli extra di array. Quindi, invece di:

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

Puoi scrivere quanto segue:

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

Questo codice ha anche un buon vantaggio in termini di prestazioni. Come nota a margine: in EcmaScript 6, puoi scrivere semplicemente:

var arr = [1, 2, 4, 5, 3]; var max = Math.max (... arr); // QUESTO ATTUALMENTE NON FUNZIONA!

Array senza fori

Tutti gli extra dell'array ignorano fori negli array. Un esempio:

var a = ["ciao",,,,, "mondo"]; // a [1] a a [4] non sono definiti var count = a.reduce (function (count) return count + 1;, 0); console.log (conteggio); // 2

Questo comportamento probabilmente ha un vantaggio in termini di prestazioni, ma ci sono casi in cui può essere un vero dolore nel sedere. Uno di questi esempi potrebbe essere quando hai bisogno di una serie di numeri casuali; non è possibile scrivere semplicemente questo:

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

Ma ricorda che puoi chiamare tutti i costruttori nativi senza nuovo. E un altro bocconcino utile: Function.prototype.apply non ignora i buchi Combinando questi, questo codice restituisce il risultato corretto:

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

Il secondo argomento sconosciuto

La maggior parte di quanto sopra è noto e utilizzato da molti programmatori su base regolare. Ciò che molti di loro non sanno (o almeno non usano) è il secondo argomento della maggior parte degli extra di array (solo il ridurre* le funzioni non lo supportano).

Usando il secondo argomento, puoi passare a Questo valore alla funzione. Di conseguenza, sei in grado di utilizzare prototipo-metodi. Ad esempio, il filtraggio di un array con un'espressione regolare diventa un elemento singolo:

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

Inoltre, controllare se un oggetto ha certe proprietà diventa un gioco da ragazzi:

["foo", "isArray", "create"]. some (Object.prototype.hasOwnProperty, Object); // restituisce true (a causa di Object.create)

Alla fine, puoi utilizzare tutti i metodi che desideri:

// lascia fare qualcosa di pazzo [function (a) return a * a; , function (b) return b * b * b; ] .map (Array.prototype.map, [1, 2, 3]); // restituisce [[1, 4, 9], [1, 8, 27]]

Questo diventa folle quando si usa Function.prototype.call. Guarda questo:

["foo", "\ n \ tbar", "\ r \ nbaz \ t"] .map (Function.prototype.call, String.prototype.trim); // restituisce ["foo", "bar", "baz"] [true, 0, null, []]. map (Function.prototype.call, Object.prototype.toString); // restituisce ["[oggetto Booleano]", "[numero oggetto]", "[oggetto Nullo]", "[oggetto Matrice]"]

Naturalmente, per far piacere al tuo geek interiore, puoi anche usarlo Function.prototype.call come secondo parametro In questo modo, ogni elemento dell'array viene chiamato con il suo indice come primo argomento e l'intero array come secondo:

[function (index, arr) // qualunque cosa si voglia fare con it. forEach (Function.prototype.call, Function.prototype.call);

Consente di costruire qualcosa di utile

Detto questo, costruiamo una semplice calcolatrice. Vogliamo solo supportare gli operatori di base (+, -, *, /), e dobbiamo rispettare la procedura dell'operatore. Quindi, moltiplicazione (*) e divisione (/) devono essere valutati prima dell'aggiunta (+) e sottrazione (-).

In primo luogo, definiamo una funzione che accetta una stringa che rappresenta il calcolo come primo e unico argomento.

funzione calcola (calcolo) 

Nel corpo della funzione, iniziamo a convertire il calcolo in una matrice usando un'espressione regolare. Quindi, ci assicuriamo di aver analizzato l'intero calcolo unendo le parti usando Array.prototype.join e confrontando il risultato con il calcolo originale.

var parts = calculation.match (// digit | operators | whitespace /(?:\-?[\d\.]+)|[-\+\*\/]|\s+/g); if (calcolo! == parts.join ("")) throw new Error ("impossibile analizzare il calcolo")

Dopo ciò, chiamiamo String.prototype.trim per ogni elemento eliminare lo spazio bianco. Quindi, filtriamo la matrice e rimuoviamo elementi falsi (es .: f stringhe vuote).

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

Ora, costruiamo un array separato che contiene numeri analizzati.

var nums = parts.map (parseFloat);

È possibile passare funzioni integrate come parseFloat senza bisogno di wrapper!

A questo punto, il modo più semplice per continuare è semplice per-ciclo continuo. Al suo interno, costruiamo un altro array (chiamato trasformati) con moltiplicazione e divisione già applicate. L'idea di base è di ridurre ogni operazione a un'aggiunta, in modo che l'ultimo passaggio diventi piuttosto banale.

Controlliamo ogni elemento del nums array per garantire che non lo sia NaN; se non è un numero, allora è un operatore. Il modo più semplice per farlo è sfruttando il fatto che, in JavaScript, NaN! == NaN. Quando troviamo un numero, lo aggiungiamo alla matrice dei risultati. Quando troviamo un operatore, lo applichiamo. Saltiamo le operazioni di aggiunta e cambiamo solo il segno del numero successivo per la sottrazione.

Moltiplicazione e divisione devono essere calcolati usando i due numeri circostanti. Poiché abbiamo già aggiunto il numero precedente all'array, è necessario rimuoverlo utilizzando Array.prototype.pop. Il risultato del calcolo viene aggiunto all'array dei risultati, pronto per essere aggiunto.

var processed = []; per (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]);   

L'ultimo passaggio è abbastanza semplice: aggiungiamo solo tutti i numeri e restituiamo il risultato finale.

return processed.reduce (function (result, elem) return result + elem;);

La funzione completata dovrebbe apparire così:

function calculate (calcolo) // crea un array contenente le singole parti var parts = calculation.match (// cifre | operatori | whitespace / (?: \ -? [\ d \.] +) | [- \ + \ * \ /] | \ s + / g); // verifica se tutto è stato abbinato se (calcolo! == parts.join ("")) genera un nuovo errore ("non è in grado di analizzare il calcolo") // rimuove tutti gli spazi bianchi part = parts.map (Function.prototype. call, String.prototype.trim); parts = parts.filter (Boolean); // costruisce un array separato contenente numeri analizzati var nums = parts.map (parseFloat); // costruisce un altro array con tutte le operazioni ridotte alle aggiunte var processed = []; per (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; ); 

Va bene, proviamolo:

calcola ("2 + 2.5 * 2") // restituisce 7 calcola ("12/6 + 4 * 3") // restituisce 14

Sembra che funzioni! Ci sono ancora alcuni casi limite che non sono gestiti, come i primi calcoli dell'operatore o numeri contenenti più punti. Il supporto per la parentesi sarebbe bello, ma non ci preoccuperemo di scavare più in dettaglio in questo semplice esempio.


Avvolgendo

Mentre gli extra degli array di ES5 potrebbero, all'inizio, sembrare abbastanza banali, rivelano un po 'di profondità, una volta data loro una possibilità. Improvvisamente, la programmazione funzionale in JavaScript diventa più che un inferno di callback e un codice spaghetti. Realizzare questo fuori è stato un vero colpo di scena per me e ha influenzato il mio modo di scrivere programmi.

Naturalmente, come visto sopra, ci sono sempre casi in cui si preferisce utilizzare un ciclo regolare. Ma, e questa è la parte bella, non è necessario.