프런트엔드 개발자 면접 단골 문제인 자바스크립트 reduce 함수 쉽게 이해하기

안녕하세요?

오늘은 프런트엔드 쪽 특히 자바스크립트 면접에 단골처럼 나오는 Array.reduce() 함수에 대해 알아보겠습니다.

Array.map() 함수는 한번 이해하면 쉬워서 누구나 쓸 수 있는데요.

Array.reduce() 함수는 조금은 어려워서 사실 쉽게 쓰지 못하고 있는데요.

한번 익혀두면 map과 함께 가장 많이 쓰이는 함수라고 합니다.

reduce 함수의 역할을 설명할 때 쓰이는 게 바로 배열의 합계인데요.

const numbers = [12, 6, 8, 41]

위와 같은 숫자로만 이루어진 배열이 있다고 할 때 이 배열의 전체 합을 구한다고 치면, 바로 reduce 함수가 적합합니다.

이걸 for 문으로 할 수도 있지만 프런트엔드 개발자라면 Array.reduce() 함수로 구현해야지 좀 더 있어 보이지 않을까요?

const sum = numbers.reduce((s, item) => {
  return s + item
}, 0)
// sum = 67

reduce 함수를 위와 같이 사용하면 배열의 전체 합을 구할 수 있습니다.

그럼 reduce 함수의 시그니쳐를 살펴볼까요?

array.reduce((previousValue, currentValue) => {
  // code here
}, initialValue)

인자로 3가지가 있는데요. previousValue, currentValue, initialValue 가 있습니다.

reduce 함수는 위의 시그니쳐에서 // code here 부분을 실행하는데요.

이 // code here 부분을 reducer 함수라고 부릅니다.

reduce 함수는 reducer 함수를 배열의 모든 원소에 대해 순서대로 실행하고, 실행 후 리턴 값을 다음 reducer 함수 실행에 넘겨줍니다.

그래서 우리가 위에서 예제로 들었던 배열 전체 합계 코드를 디버깅해보면 아래와 같습니다.

const numbers = [12, 6, 8, 41]

const sum = numbers.reduce((s, item) => {
  return s + item
}, 0)

// step 1
reduce((0 /* initial value */ , numbers[0]) => {
    return 0 + 12
}

// step 2
reduce((12 /* previous value */ , numbers[1]) => {
    return 12 + 6
}

// step 3
reduce((18 /* previous value */ , numbers[2]) => {
    return 18 + 8
}

// step 4
reduce((26 /* previous value */ , numbers[3]) => {
    return 26 + 41
}
Stepsitem
Step 1012
Step 2126
Step 3188
Step 42641

step 1을 보시면 initial value로 0을 넣고 currentValue 에 number[0] 즉, 배열의 첫 번째 항목을 넣고, 그다음 reducer 함수를 실행합니다.

이렇게 되면 reducer 함수에는 return 0 + 12 가 들어가게 되고 0 + 12의 결과 값이 바로 step 2의 previousValue 의 값으로 들어갑니다.

step 2를 보시면 step 1의 reducer 함수 실행 값이 12가 step 2에서는 initialValue 로 자리 잡게 되고 currentValue 는 step 2이기 때문에 배열의 두 번째 항목인 number[1] 이 배당됩니다.

그래서 step 2의 reducer 함수에는 12 + 6 이 오게 됩니다.

이렇게 step 2가 끝나면 step 3, step 4가 실행됩니다.

배열의 원소 개수가 4개이기 때문에 step 4까지만 실행되고요.


생략할 수 있는 initialValue

initialValue 는 optional입니다. 즉 생략할 수 있죠.

생략하면 그 자리에 배열의 첫 번째 항목이 들어가고 currentValue 에는 두 번째 항목이 들어가게 됩니다.

아래 코드처럼 s는 array[0] 이, item은 array[1] 이 들어가게 되죠.

const numbers = [12, 6, 8, 41]
const sum = numbers.reduce((s, item) => {
  // s = array[0]
  // item = array[1]
})

그러면 어떻게 되는지 디버깅해 볼까요?

const numbers = [12, 6, 8, 41]

const sum = numbers.reduce((s, item) => {
  return s + item
});

// step 1
reduce((12 /* initial value */ , numbers[1]) => {
    return 12 + 6
}

// step 2
reduce((18 /* previous value */ , numbers[2]) => {
    return 18 + 8
}

// step 3
reduce((26 /* previous value */ , numbers[3]) => {
    return 26 + 41
}
Stepsitem
Step 1126
Step 2188
Step 32641

initialValue 를 생략하니까 step이 3가지로 줄었는데요.

initialValue 를 생략할지 말지는 코드를 잘 생각하시고 쓰시면 됩니다.


reduce로 배열 리턴하기

reduce 함수는 배열의 항목을 가공한 후 값을 도출해서 리턴하는 함수인데요.

사실 리턴할 때 값(value)뿐만 아니라 배열(array)도 리턴할 수 있습니다.

배열에서 짝수만 골라서 다시 배열로 리턴하려고 한다면 아래와 같이 하면 됩니다.

const numbers = [12, 6, 8, 41]
const evenNums = numbers.reduce((evenArr, item) => {
  if (item % 2 == 0) {
    evenArr.push(item)
  }
  return evenArr
}, [])

// note the passing of [] as the initial value
// evenNums = [12, 6, 8]

이것도 한번 step 별로 디버깅해볼까요?

const numbers = [12, 6, 8, 41]

const evenNums = numbers.reduce((evenArr, item) => {
    if (item % 2 == 0) {
        evenArr.push(item)
    }
    return evenArr
}, [])

// step 1
reduce(([] /* initial value */ , numbers[0]) => {
    if (12 % 2 == 0) {
        evenArr.push(item) // [].push(12)
    }
    return evenArr // return [12]
}

// step 2
reduce(([12] /* previous value */ , numbers[1]) => {
    if (6 % 2 == 0) {
        evenArr.push(item) // [12].push(6)
    }
    return evenArr // return [12,6]
}

// step 3
reduce(([12,6] /* previous value */ , numbers[2]) => {
    if (8 % 2 == 0) {
        evenArr.push(item) // [12,6].push(8)
    }
    return evenArr // return [12,6,8]
}

// step 4
reduce(([12,6,8] /* previous value */ , numbers[3]) => {
    if (41 % 2 == 0) {
        evenArr.push(item) // no execution
    }
    return evenArr // return [12,6,8]
}
Stepsitem
Step 1[]12
Step 2[12]6
Step 3[12,6]8
Step 4[12,6,8]41

spep 별로 살펴보면 위와 같이 되는데요.

충분히 이해하실 수 있을 겁니다.


reduce 함수의 Full 시그니쳐

사실 Array.reduce 함수의 Full 시그니쳐는 다음처럼 4개 인자를 갖습니다.

Array.reduce((previousValue, currentValue, currentIndex, array) => {
  // code here
}, initialValue)

currentIndex 는 0부터 시작하는 현재의 step을 나타냅니다. 몇 단계인지 알려주죠.

array는 전체 배열을 리턴해 줍니다. 어떨 때 쓰이냐면 reduce로 배열 자체를 조작했을 때 원본 배열을 알고 싶을 때 쓰는 거죠.

예를 들어볼까요?

const numbers = [12, 6, 8, 41]
const sum = numbers.reduce((s, item, currentIndex, array) => {
  return s + item
}, 0)

위 코드를 Step 별로 분석하면 아래와 같습니다.

StepsitemcurrentIndexarray
Step 10120[12,6,8,41]
Step 21261[12,6,8,41]
Step 31882[12,6,8,41]
Step 426413[12,6,8,41]

지금까지 reduce 함수를 알아봤는데요.

꼭 숙달하여 면접 잘 보시기 바랍니다.