JavaScript 배열 고차 함수
JavaScript 배열 고차 함수에 대해 알아보겠습니다. JavaScript 배열의 고차 함수로는 Map, Set, Reduce, Sort, Filter 등이 있습니다. 고차 함수는 함수를 파라미터로 전달받거나 연산의 결과로 반환해주는 역할을 합니다. JavaScript는 다양한 고차 함수를 지원하고 있는데 그 중 Array 객체의 유용한 고차 함수에 대해 정리해보겠습니다.
1. Array.forEach()
forEach() 메서드는 for문을 대체하는 고차 함수입니다. 반복문을 추상화하여 구현된 메서드이고 내부에서 주어진 배열을 순회하면서 연산을 수행합니다. for문과는 다르게 break, continue를 사용할 수 없어서 배열의 모든 요소를 중단없이 순회한다는 특징을 갖고 있습니다. for문 보다는 성능이 좋지는 않지만 가독성이 더 좋고 함수형 프로그래밍에 부합하기 때문에 꼭 필요한 경우가 아니라면 for문을 대신하여 사용할 것이 권장됩니다.
1
2
3
4
5
6
7
8
|
const numberArr = [1, 2, 3, 4, 5];
let total = 0;
numberArr.forEach((item) => {
total += item;
});
console.log(total); // 15
|
cs |
forEach() 메서드의 콜백 함수에는 다음과 같이 주어진 배열의 요소값, 인덱스, this(주어진 배열) 3개의 파라미터를 순차적으로 전달할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
|
const numberArr = [1, 2, 3, 4, 5];
let total = 0;
numberArr.forEach((item, index, thisArr) => {
console.log('thisArr', thisArr);
console.log('index', index);
console.log('item', item);
total += item;
});
console.log(total); // 15
|
cs |
배열의 원소 위치가 연속적이지 않은 희소 배열의 경우 존재하지 않는 요소는 순회 대상에서 제외됩니다. 이는 map(), reduce(), filter() 메서드에서도 동일하게 적용됩니다.
1
2
3
4
|
const numberArr = [1, , 3];
numberArr.forEach((item) => {
console.log('item', item); // item 1, item 3
});
|
cs |
2. Array.map()
map() 메서드는 주어진 배열을 순회하면서 파라미터로 전달받은 콜백 함수를 반복하여 호출합니다. 호출된 콜백 함수의 결과로 새로운 배열을 생성하여 반환하며 원본 배열은 변경되지 않습니다.
1
2
3
4
5
6
7
|
const numberArr = [1, 2, 3, 4, 5];
const numberMapArr = numberArr.map((item) => {
return (item % 2 === 0) ? 'even' : 'odd';
});
console.log(numberArr); // [1, 2, 3, 4, 5]
console.log(numberMapArr); // ['odd', 'even', 'odd', 'even', 'odd']
|
cs |
또한 map() 메서드의 콜백 함수에도 forEach() 메서드와 마찬가지로 주어진 배열의 요소값, 인덱스, this(주어진 배열) 3개의 파라미터를 순차적으로 전달할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
|
const numberArr = [1, 2, 3, 4, 5];
const numberMapArr = numberArr.map((item, index, thisArr) => {
console.log('thisArr', thisArr);
console.log('index', index);
console.log('item', item);
return (item % 2 === 0) ? 'even' : 'odd';
});
console.log(numberArr); // [1, 2, 3, 4, 5]
console.log(numberMapArr); // ['odd', 'even', 'odd', 'even', 'odd']
|
cs |
예제에서는 화살표 함수를 사용하여 콜백 함수를 구현하였는데, 화살표 함수는 함수 자체의 this를 갖지 않기 때문에 상위 스코프의 this를 참조하게 됩니다. 화살표 함수를 사용하면 코드를 간결하게 하고 콜백 함수 내부의 this가 외부의 this와 달라서 발생하는 문제를 방지할 수 있습니다.
2.1. forEach()와 map()의 차이
두 메서드 모두 배열을 순회하는 것은 동일하지만, forEach()의 경우 각 요소를 참조한 연산이 이루어지고 map()의 경우엔 각 요소를 다른 값으로 맵핑한 새로운 배열이 반환되는 점에 차이가 있습니다.
따라서, forEach()는 return value가 undefined 이고 map()은 콜백 함수의 결과값으로 구성된 새로운 배열이 반환됩니다. 정리하면 forEach()는 반복문을 대체하여 사용하고 map()은 연산의 결과로 새로운 배열을 생성하고자할 때 사용됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
|
const numberArr = [1, 2, 3, 4, 5];
const numberForEachArr = numberArr.forEach((item) => {
return item;
});
const numberMapArr = numberArr.map((item) => {
return item;
});
console.log(numberForEachArr); // undefined
console.log(numberMapArr); // [1, 2, 3, 4, 5]
|
cs |
3. Array.filter()
filter() 메서드는 주어진 배열을 순회하면서 콜백 함수의 반환값이 true에 해당하는 요소로만 구성된 새로운 배열을 생성하여 반환합니다. map() 메서드와 마찬가지로 원본 배열은 변경되지 않습니다.
1
2
3
4
5
6
7
|
const numberArr = [1, 2, 3, 4, 5];
const numberFilterArr = numberArr.filter((item) => {
return item % 2 === 0;
});
console.log(numberArr); // [1, 2, 3, 4, 5]
console.log(numberFilterArr); // [2, 4]
|
cs |
앞서 살펴본 forEach(), map() 메서드와 마찬가지로 filter() 메서드의 콜백 함수도 주어진 배열의 요소값, 인덱스, this(주어진 배열) 3개의 파라미터를 순차적으로 전달받을 수 있습니다.
1
2
3
4
5
6
7
8
9
10
|
const numberArr = [1, 2, 3, 4, 5];
const numberFilterArr = numberArr.filter((item, index, thisArr) => {
console.log('thisArr', thisArr);
console.log('index', index);
console.log('item', item);
return (item % 2 === 0);
});
console.log(numberArr); // [1, 2, 3, 4, 5]
console.log(numberFilterArr); // [2, 4]
|
cs |
또한 다음과 같이 주어진 배열에서 특정 요소 제거에 사용할 수도 있는데 중복된 요소가 있는 경우엔 모두 제거합니다. 콜백 함수의 조건이 동일하고 배열의 요소를 모두 순회하기 때문에 중복된 요소는 모두 제거됩니다. 특정 요소 하나만 제거하려면 indexOf() 메서드로 해당 요소의 index를 받아와서 splice() 메서드로 제거해줘야 합니다
1
2
3
4
5
6
7
|
const numberArr = [1, 2, 2, 3, 2, 2, 5];
const numberFilterArr = numberArr.filter((item) => {
return item != 2;
});
console.log(numberArr); // [1, 2, 2, 3, 2, 2, 5]
console.log(numberFilterArr); // [1, 3, 5]
|
cs |
4. Array.reduce()
reduce() 메서드는 주어진 배열을 순회하면서 콜백 함수가 호출되는데 이전 콜백 함수의 실행된 반환값을 전달 받은 연산의 결과값이 반환됩니다. 앞서 알아본 forEach(), map(), filter() 메서드를 대체할 수 있는 유연한 메서드이지만 상황에 따라 적절하게 사용하는 것이 좋습니다.
reduce() 메서드의 콜백 함수의 파라미터로는 이전 콜백 함수의 반환값, 주어진 배열의 현재 요소, 주어진 배열의 현재 인덱스, this(주어진 배열) 4개를 각각 순차적으로 전달할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
const numberArr = [1, 2, 3, 4, 5];
const sum = numberArr.reduce((previousValue, currentValue, currentIndex, thisArray) => {
console.log('Current Index: ' + currentIndex + ' / Previous Value: ' + previousValue + ' / Current Value: ' + currentValue);
return previousValue + currentValue;
}, 0);
console.log('Sum: ' + sum);
/*
Current Index: 0 / Previous Value: 0 / Current Value: 1
Current Index: 1 / Previous Value: 1 / Current Value: 2
Current Index: 2 / Previous Value: 3 / Current Value: 3
Current Index: 3 / Previous Value: 6 / Current Value: 4
Current Index: 4 / Previous Value: 10 / Current Value: 5
Sum: 15
*/
|
cs |
위의 예제에서 사용된 reduce() 메서드 콜백 함수는 다음과 같은 호출 과정이 이루어집니다.
호출 순서 | previousValue | currentValue | currentIndex | thisArray | 콜백 함수 반환값 (previousValue + currentValue) |
1 | 0 | 1 | 0 | [1, 2, 3, 4, 5] | 1 |
2 | 1 | 2 | 1 | [1, 2, 3, 4, 5] | 3 |
3 | 3 | 3 | 2 | [1, 2, 3, 4, 5] | 6 |
4 | 6 | 4 | 3 | [1, 2, 3, 4, 5] | 10 |
5 | 10 | 5 | 4 | [1, 2, 3, 4, 5] | 15 |
reduce() 메서드는 두번째 파라미터로 초기값을 전달할 수 있습니다. 객체의 프로퍼티를 합산하거나 배열, 객체 등으로 연산의 결과값을 반환 받는 경우에는 반드시 초기값을 전달해야 합니다. 따라서 reduce() 메서드 사용시에는 초기값을 함께 전달해주는 것이 권장됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
const numberArr = [];
const sum = numberArr.reduce((previousValue, currentValue) => {
return previousValue + currentValue;
});
// ERROR TypeError: Reduce of empty array with no initial value
const objectArr = [
{ name: 'Harry', age: 20 },
{ name: 'Kim', age: 30 },
{ name: 'Steve', age: 40 }
];
const ageSum = objectArr.reduce((previousValue, currentValue) => {
return previousValue + currentValue.age;
});
// ERROR Operator '+' cannot be applied to types '{ name: string; age: number; }' and 'number'.
|
cs |
위의 예제에서 객체의 프로퍼티를 합산하는 경우 콜백 함수의 previousValue에는 객체가 아닌 숫자가 전달되게 됩니다. 전달 받은 previousValue.age는 undefined이고 TypeScript에서 실행하는 경우엔 type이 맞지 않아 연산 에러가 발생합니다. 따라서 위의 예시처럼 reduce() 메서드를 사용하는 경우에는 초기값을 함께 전달해주어야 합니다.
5. Array.find()
find() 메서드는 주어진 배열을 순회하면서 콜백 함수 실행의 반환값이 true에 해당하는 첫번째 요소를 반환합니다.
find() 메서드의 콜백 함수에는 주어진 배열의 요소값, 인덱스, this(주어진 배열) 3개의 파라미터를 순차적으로 전달할 수 있습니다. 또한 find() 메서드와는 달리 filter() 메서드는 반환값이 true로만 구성된 새로운 배열을 생성하여 반환한다는 점에서 차이가 있습니다.
1
2
3
4
5
6
7
8
9
10
|
const numberArr = [1, 3, 3, 5, 7];
const objectArr = [
{ name: 'Harry', age: 20 },
{ name: 'Kim', age: 30 },
{ name: 'Steve', age: 40 }
];
console.log(objectArr.find(item => item.age === 20)); // {name: "Harry", age: 20}
console.log(numberArr.find(item => item === 3)); // 3
console.log(numberArr.filter(item => item === 3)); // [3, 3]
|
cs |
6. Array.findIndex()
findIndex() 메서드는 주어진 배열을 순회하면서 콜백 함수 실행의 반환값이 true에 해당하는 첫번째 요소의 index를 반환합니다. 콜백 함수 실행의 반환값이 true인 요소가 없는 경우엔 -1을 반환합니다.
findIndex() 메서드의 콜백 함수에는 다음과 같이 주어진 배열의 요소값, 인덱스, this(주어진 배열) 3개의 파라미터를 순차적으로 전달할 수 있습니다.
1
2
3
4
5
6
7
8
|
const objectArr = [
{ name: 'Harry', age: 20 },
{ name: 'Kim', age: 30 },
{ name: 'Steve', age: 40 }
];
console.log(objectArr.findIndex(item => item.age === 20)); // 0
console.log(objectArr.findIndex(item => item.name === 'Kim')); // 1
|
cs |
이상으로 JavaScript 배열의 고차 함수에 대해 알아봤습니다.
※ Reference
- 이웅모 지음, 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p529 ~ p551. 27.9 배열 고차 함수
- poiemaweb.com, 배열 고차 함수, https://poiemaweb.com/js-array-higher-order-function
- dongmin-jang.medium.com, [Javascript] map, reduce, filter를 유용하게 활용하는 15가지 방법, https://medium.com/@Dongmin_Jang/javascript-15%EA%B0%80%EC%A7%80-%EC%9C%A0%EC%9A%A9%ED%95%9C-map-reduce-filter-bfbc74f0debd
- bblog.tistory.com, 자바스크립트의 유용한 배열 메소드 사용하기... map(), filter(), find(), reduce(), https://bblog.tistory.com/300
- velog.io/@lilyoh, [js] array 의 map 메서드, https://velog.io/@lilyoh/js-array-%EC%9D%98-map-%EB%A9%94%EC%84%9C%EB%93%9C
- radlohead.github.io, javascript에서 forEach, map, reduce 차이점을 알아보자, https://radlohead.github.io/front-end/javascript-forEach-map-reduce.html