Array.prototype.reduce()

Questo contenuto viene visualizzato in inglese perché non è ancora disponibile una versione tradotta nella lingua selezionata. Aiutaci a tradurre questo articolo!

Il metodo reduce() esegue una funzione reducer (che tu fornisci) su ogni elemento dell'array, risultante in un unico output.

La funzione reducer accetta quattro argomenti:

  1. Accumulatore (acc)
  2. Valore corrente (cur)
  3. Indice corrente (idx)
  4. Array sul quale viene eseguito il metodo (src)

Il valore restituito della funzione reducer viene assegnato all'accumulatore, il cui valore viene memorizzato attraverso ogni iterazione nell'intero array e in definitiva, diventa il valore finale finale singolo.

Sintassi

arr.reduce(callback(accumulator, currentValue[, index[, array]]), [, initialValue])

Parametri

callback
Una funzione da eseguire su ogni elemento dell'array (tranne il primo, se non viene fornita initialValue), accetta 4 argomenti:
accumulator
L'accumulatore accumula i valori di ritorno del callback. È il valore accumulato precedentemente restituito nell'ultima chiamata del callback o initialValue, se fornito (vedere di seguito).
currentValue
L'elemento corrente in elaborazione nell'array.
index Optional
L'indice dell'elemento corrente in elaborazione nell'array. Inizia dall'indice 0 se viene fornito initialValue Altrimenti, inizia dall'indice 1.
array Optional
L'array a cui viene applicato reduce().
initialValue Optional
Un valore da utilizzare come primo argomento della prima chiamata del callback. Se non viene fornito initialValue il primo elemento dell'array verrà utilizzato e saltato. Chiamare reduce() su un array vuoto senza initialValue genererà un TypeError.

Valore di ritorno

Il singolo valore che risulta dalla riduzione.

Descrizione

Il metodo reduce() esegue il callback una volta per ogni valore assegnato presente nell'array, prendendo quattro argomenti:

  • accumulator
  • currentValue
  • currentIndex
  • array

La prima volta che viene chiamato il callback, accumulator e currentValue possono essere uno dei due valori. Se initialValue viene fornito nella chiamata a reduce(), accumulator sarà uguale a initialValue, e currentValue sarà uguale al primo valore nell'array. Se non viene fornito alcun valore initialValue, accumulator sarà uguale al primo valore dell'array e currentValue sarà uguale al secondo.

Note: Se non viene fornito initialValue, reduce() eseguirà la funzione di callback a partire dall'indice 1, saltando il primo indice. Se viene fornito initialValue, inizierà dall'indice 0.

Se l'array è vuoto e non viene fornito initialValue verrà generato un TypeError. Se l'array ha solo un elemento (indipendentemente dalla posizione) e non viene fornito initialValue, o se è fornito initialValue ma l'array è vuoto, il valore solo verrà restituito senza chiamare callback.

Di solito è più sicuro fornire initialValue perché ci sono tre output possibili senza initialValue, come mostrato nell'esempio seguente.

var maxCallback = ( acc, cur ) => Math.max( acc.x, cur.x );
var maxCallback2 = ( max, cur ) => Math.max( max, cur );

// reduce() without initialValue
[ { x: 22 }, { x: 42 } ].reduce( maxCallback ); // 42
[ { x: 22 }            ].reduce( maxCallback ); // { x: 22 }
[                      ].reduce( maxCallback ); // TypeError

// map/reduce; better solution, also works for empty or larger arrays
[ { x: 22 }, { x: 42 } ].map( el => el.x )
                        .reduce( maxCallback2, -Infinity );

Come funziona reduce()

Supponiamo che si sia verificato il seguente uso di reduce():

[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array) {
  return accumulator + currentValue;
});

Il callback verrebbe invocato quattro volte, con gli argomenti e i valori restituiti in ogni chiamata come segue:

callback accumulator currentValue currentIndex array valore ritornato
prima chiamata 0 1 1 [0, 1, 2, 3, 4] 1
seconda chiamata 1 2 2 [0, 1, 2, 3, 4] 3
terza chiamata 3 3 3 [0, 1, 2, 3, 4] 6
quarta chiamata 6 4 4 [0, 1, 2, 3, 4] 10

Il valore restituito da reduce() sarà quello dell'ultima chiamata del callback (10).

È inoltre possibile fornire una Arrow Function al posto di una funzione completa. Il seguente codice produrrà lo stesso output del codice nel blocco sopra riportato:

[0, 1, 2, 3, 4].reduce( (accumulator, currentValue, currentIndex, array) => accumulator + currentValue );

Se dovessi fornire initialValue come secondo argomento a reduce(), il risultato sarebbe simile a questo:

[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
    return accumulator + currentValue;
}, 10);
callback accumulator currentValue currentIndex array valore restituito
prima chiamata 10 0 0 [0, 1, 2, 3, 4] 10
seconda chiamata 10 1 1 [0, 1, 2, 3, 4] 11
terza chiamata 11 2 2 [0, 1, 2, 3, 4] 13
quarta chiamata 13 3 3 [0, 1, 2, 3, 4] 16
quinta chiamata 16 4 4 [0, 1, 2, 3, 4] 20

Il valore restituito da reduce() in questo caso sarebbe 20.

Esempi

Sommare tutti i valori di un array

var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
  return accumulator + currentValue;
}, 0);
// sum è 6

In alternativa scritto con una arrow function:

var total = [ 0, 1, 2, 3 ].reduce(
  ( accumulator, currentValue ) => accumulator + currentValue,
  0
);

Somma dei valori in un array di oggetti

Per riassumere i valori contenuti in un array di oggetti, devi fornire initialValue, in modo che ogni elemento passi attraverso la tua funzione

var initialValue = 0;
var sum = [{x: 1}, {x: 2}, {x: 3}].reduce(function (accumulator, currentValue) {
    return accumulator + currentValue.x;
},initialValue)

console.log(sum) // logs 6

In alternativa scritto con una arrow function:

var initialValue = 0;
var sum = [{x: 1}, {x: 2}, {x: 3}].reduce(
    (accumulator, currentValue) => accumulator + currentValue.x
    ,initialValue
);

console.log(sum) // logs 6

Appiattire una serie di array

var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  function(accumulator, currentValue) {
    return accumulator.concat(currentValue);
  },
  []
);
// flattened is [0, 1, 2, 3, 4, 5]

In alternativa scritto con una arrow function:

var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  ( accumulator, currentValue ) => accumulator.concat(currentValue),
  []
);

Conteggio delle istanze di valori in un oggetto

var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

var countedNames = names.reduce(function (allNames, name) { 
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

Raggruppamento di oggetti in base a una proprietà

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];

function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// { 
//   20: [
//     { name: 'Max', age: 20 }, 
//     { name: 'Jane', age: 20 }
//   ], 
//   21: [{ name: 'Alice', age: 21 }] 
// }

Array di legame contenuti in una serie di oggetti che utilizzano lo spread operator e initialValue

// friends - an array of objects 
// where object field "books" - list of favorite books 
var friends = [{
  name: 'Anna',
  books: ['Bible', 'Harry Potter'],
  age: 21
}, {
  name: 'Bob',
  books: ['War and peace', 'Romeo and Juliet'],
  age: 26
}, {
  name: 'Alice',
  books: ['The Lord of the Rings', 'The Shining'],
  age: 18
}];

// allbooks - list which will contain all friends' books +  
// additional list contained in initialValue
var allbooks = friends.reduce(function(accumulator, currentValue) {
  return [...accumulator, ...currentValue.books];
}, ['Alphabet']);

// allbooks = [
//   'Alphabet', 'Bible', 'Harry Potter', 'War and peace', 
//   'Romeo and Juliet', 'The Lord of the Rings',
//   'The Shining'
// ]

Rimuovi gli elementi duplicati nell'array

Note: Se si utilizza un ambiente compatibile con Set e Array.from(), è possibile utilizzare let orderedArray = Array.from(new Set(myArray)); per ottenere un array in cui sono stati rimossi gli elementi duplicati.

var myArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e', 'c', 'd', 'd', 'd', 'd'];
var myOrderedArray = myArray.reduce(function (accumulator, currentValue) {
  if (accumulator.indexOf(currentValue) === -1) {
    accumulator.push(currentValue);
  }
  return accumulator
}, [])

console.log(myOrderedArray);

Eseguire le Promises in Sequenza

/**
 * Esegue promises da un array di funzioni che possono restituire promises
 * in modo concatenato
 *
 * @param {array} arr - promise arr
 * @return {Object} promise object
 */
function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(input)
  );
}

// promise function 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5);
  });
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// function 3  - sarà avvolta in una promise risolta da .then()
function f3(a) {
 return a * 3;
}

// promise function 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10)
  .then(console.log);   // 1200

Composizione funzionale per tubazioni

// Elementi da utilizzare per la composizione
const double = x => x + x;
const triple = x => 3 * x;
const quadruple = x => 4 * x;

// Function composition enabling pipe functionality
const pipe = (...functions) => input => functions.reduce(
    (acc, fn) => fn(acc),
    input
);

// Funzioni composte per la moltiplicazione di valori specifici
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);

// Utilizzo
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240

Scrivere map usando reduce

if (!Array.prototype.mapUsingReduce) {
  Array.prototype.mapUsingReduce = function(callback, thisArg) {
    return this.reduce(function(mappedArray, currentValue, index, array) {
      mappedArray[index] = callback.call(thisArg, currentValue, index, array);
      return mappedArray;
    }, []);
  };
}

[1, 2, , 3].mapUsingReduce(
  (currentValue, index, array) => currentValue + index + array.length
); // [5, 7, , 10]

Polyfill

// Production steps of ECMA-262, Edition 5, 15.4.4.21
// Reference: http://es5.github.io/#x15.4.4.21
// https://tc39.github.io/ecma262/#sec-array.prototype.reduce
if (!Array.prototype.reduce) {
  Object.defineProperty(Array.prototype, 'reduce', {
    value: function(callback /*, initialValue*/) {
      if (this === null) {
        throw new TypeError( 'Array.prototype.reduce ' + 
          'called on null or undefined' );
      }
      if (typeof callback !== 'function') {
        throw new TypeError( callback +
          ' is not a function');
      }

      // 1. Let O be ? ToObject(this value).
      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0; 

      // Steps 3, 4, 5, 6, 7      
      var k = 0; 
      var value;

      if (arguments.length >= 2) {
        value = arguments[1];
      } else {
        while (k < len && !(k in o)) {
          k++; 
        }

        // 3. If len is 0 and initialValue is not present,
        //    throw a TypeError exception.
        if (k >= len) {
          throw new TypeError( 'Reduce of empty array ' +
            'with no initial value' );
        }
        value = o[k++];
      }

      // 8. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kPresent be ? HasProperty(O, Pk).
        // c. If kPresent is true, then
        //    i.  Let kValue be ? Get(O, Pk).
        //    ii. Let accumulator be ? Call(
        //          callbackfn, undefined,
        //          « accumulator, kValue, k, O »).
        if (k in o) {
          value = callback(value, o[k], k, o);
        }

        // d. Increase k by 1.      
        k++;
      }

      // 9. Return accumulator.
      return value;
    }
  });
}

Se hai bisogno di supportare motori JavaScript veramente obsoleti che non supportano Object.defineProperty(), è meglio non applicare polyfills ai metodi di Array.prototype, poiché non puoi renderli non enumerabili.

Specifiche

Specifica Stato Commento
ECMAScript 5.1 (ECMA-262)
The definition of 'Array.prototype.reduce()' in that specification.
Standard Definizione iniziale Implementato in JavaScript 1.8.
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Array.prototype.reduce()' in that specification.
Standard
ECMAScript Latest Draft (ECMA-262)
The definition of 'Array.prototype.reduce()' in that specification.
Draft

Compatibilità con i browser

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
reduceChrome Full support 3Edge Full support 12Firefox Full support 3IE Full support 9Opera Full support 10.5Safari Full support 4.1WebView Android Full support ≤37Chrome Android Full support 18Firefox Android Full support 4Opera Android Full support YesSafari iOS Full support 4Samsung Internet Android Full support 1.0nodejs Full support Yes

Legend

Full support  
Full support

Vedi anche