reduce()메서드는 누산기(accumulator)와 배열의 각 요소(왼쪽에서 오른쪽으로)에 대해 하나의 단일 값(single value)으로 줄이는 함수를 적용합니다.

문법(Syntax)

arr.reduce(callback[, initialValue])

매개변수

callback
배열의 각 (요소) 값에 대해 실행할 함수. 인수(argument) 4개를 취하며, 각 인수는 다음과 같습니다 :
accumulator
누산기(accumulator)는 콜백의 반환값을 누적합니다. 초기값(initialValue)이 주어진 경우에는 그 값(아래 참조) 또는 콜백의 마지막 호출에서 이전에 반환된 누적값입니다.
currentValue
배열 내 현재 처리되고 있는 요소.
currentIndexOptional
배열 내 현재 처리되고 있는 요소의 인덱스. 초기값(initialValue)이 주어진 경우 0부터, 그렇지 않으면 1부터 시작합니다.
arrayOptional
reduce()가 호출된 배열.
initialValueOptional
콜백(callback)의 첫 번째 호출에서 첫 번째 인수로 사용되는 값. 초기값이 주어지지 않은 경우에는 배열의 첫 번째 요소가 사용됩니다. 빈(empty) 배열에서 초기값 없이 reduce()를 호출하면 에러가 발생합니다.

반환 값

누적 계산의 결과를 반환 합니다.

설명

reduce는 빈 요소를 제외하고 배열 내에 존재하는 각 요소에 대해 콜백(callback) 함수를 한 번씩 실행하는데, 콜백 함수는 다음의 네 인수를 받습니다:

  • accumulator
  • currentValue
  • currentIndex
  • array

콜백이 처음으로 호출될 때, accumulator와 currentValue는 두 가지 값 중 하나를 가질 수 있습니다. 만약 reduce() 함수 호출에서 initialValue가 주어진 경우에는 accumulatorinitialValue와 같고 currentValue는 배열의 첫 번째 값과 같습니다. initialValue가 주어지지 않은 경우라면, accumulator는 배열의 첫 번째 값과 같고 currentValue는 두 번째와 같습니다.

참고: 초기값(initialValue)이 주어지지 않으면, reduce()는 인덱스 1부터 시작해 콜백 함수를 실행하고, 첫 번째 인덱스는 건너 뛸 것입니다. 초기값(initialValue)이 주어진 경우라면, 인덱스 0에서 시작할 것입니다.

빈 배열에 초기값(initialValue)이 주어지지 않은 경우, TypeError가 발생합니다. 배열의 요소가 (위치와 관계없이) 하나 뿐이면서 초기값(initialValue)이 제공되지 않은 경우나 초기값(initialValue)은 주어졌으나 배열이 빈 경우, 그 단독 값이 callback 호출 없이 반환됩니다.

다음의 예제에서처럼, 초기값(initialValue)을 주지 않을 경우에는 출력 가능한 형식이 세 가지가 되므로 일반적으로 초기값을 주는 것이 더 안전합니다.

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 arrays
[ { x: 22 }, { x: 42 } ].map( el => el.x )
                        .reduce( maxCallback2, -Infinity );

reduce() 작동 방식

다음의 예제를 생각해 봅시다.

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

콜백은 4번 호출됩니다. 각 호출의 인수와 반환값은 다음과 같습니다.

callback accumulator currentValue currentIndex array 반환값
1번째 호출 0 1 1 [0, 1, 2, 3, 4] 1
2번째 호출 1 2 2 [0, 1, 2, 3, 4] 3
3번째 호출 3 3 3 [0, 1, 2, 3, 4] 6
4번째 호출 6 4 4 [0, 1, 2, 3, 4] 10

reduce에 의해 반환되는 값은 마지막 콜백 호출의 반환값 (10)이 됩니다.

완전한 함수 대신에 화살표 함수를 제공할 수도 있습니다. 아래 코드는 위의 코드와 같은 출력물을 냅니다:

[0, 1, 2, 3, 4].reduce( (prev, curr) => prev + curr );

reduce의 두 번째 인수로 초기값을 제공하는 경우, 결과는 다음과 같습니다:

[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array) {
  return accumulator + currentValue;
}, 10);
  accumulator currentValue currentIndex array 반환값
1번째 호출 10 0 0 [0, 1, 2, 3, 4] 10
2번째 호출 10 1 1 [0, 1, 2, 3, 4] 11
3번째 호출 11 2 2 [0, 1, 2, 3, 4] 13
4번째 호출 13 3 3 [0, 1, 2, 3, 4] 16
5번째 호출 16 4 4 [0, 1, 2, 3, 4] 20

reduce가 결과로 반환된 값은 이 경우 20이 됩니다.

예제

배열의 모든 값 합산

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

화살표 함수(arrow function)로도 작성할 수 있습니다:

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

객체 배열에서의 값 합산

객체로 이루어진 배열에 들어 있는 값을 합산하기 위해서는 반드시 초기값을 주어 각 항목이 여러분의 함수를 거치도록 해야 합니다.

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

화살표 함수(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

배열로 이루어진 배열의 펼치기(flatten)

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

화살표 함수(arrow function)로도 작성할 수 있습니다:

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

객체 내의 값 인스턴스 개수 세기

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 }

속성으로 객체 분류하기

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 }] 
// }

확장 연산자와 초기값을 이용하여 객체로 이루어진 배열에 담긴 배열 연결하기

// 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'
// ]

 

배열의 중복 항목 제거

let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
let result = arr.sort().reduce((accumulator, current) => {
    const length = accumulator.length
    if (length === 0 || accumulator[length - 1] !== current) {
        accumulator.push(current);
    }
    return accumulator;
}, []);
console.log(result); //[1,2,3,4,5]

약속을 순차적으로 실행하기

/**
 * Runs promises from array of functions that can return promises
 * in chained manner
 *
 * @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  - will be wrapped in a resolved promise by .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

파이프 기능을 이용한 함수 구성

// Building-blocks to use for composition
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
);

// Composed functions for multiplication of specific values
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);

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

reduce를 이용한 map 작성

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)1

// ECMA-262의 진행 단계, 5판(Edition), 15.4.4.21
// 참조: 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;
    }
  });
}

Object.defineProperty()가 지원되지 않는 아주 오래된(truly obsolete) 자바스크립트 엔진을 지원해야 하는 경우라면, Array.prototype의 메서드를 비열거형으로 만들 수 없으므로 폴리필(pollyfill)하는데 일절 사용하지 않는 것이 좋습니다. (If you need to support truly obsolete JavaScript engines that don't support Object.defineProperty(), it's best not to polyfill Array.prototype methods at all, as you can't make them non-enumerable.)

각주 1: 폴리필(polyfill) 또는 폴리파일러(polyfiller)는 개발자의 입장에서 브라우저가 기본적으로 지원할 것으로 기대하는 코드의 일부분(또는 플러그인)을 말한다. 브라우저에 따라 지원되는 기능이나 API가 다를 수 있는데, 이를 보완할 수 있는 브라우저에 상관없이 동일한 개발 환경을 지원하는 코드나 플러그인을 제공하는 것이다. 대표적인 폴리필로는 모더나이저(Modernizr)가 있다.

사양

Specification Status Comment
ECMAScript 5.1 (ECMA-262)
The definition of 'Array.prototype.reduce()' in that specification.
Standard Initial definition. Implemented 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  

브라우저 호환성

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidEdge MobileFirefox for AndroidOpera for AndroidiOS SafariSamsung InternetNode.js
Basic supportChrome Full support YesEdge Full support YesFirefox Full support 3IE Full support 9Opera Full support 10.5Safari Full support 4WebView Android Full support YesChrome Android Full support YesEdge Mobile Full support YesFirefox Android Full support 4Opera Android Full support YesSafari iOS Full support YesSamsung Internet Android Full support Yesnodejs Full support Yes

Legend

Full support  
Full support

같이 보기

문서 태그 및 공헌자

이 페이지의 공헌자: dolmoon, KisukPark, ieay4a, Violetdusk, Netaras, preco21
최종 변경자: dolmoon,