
오늘은 코딩 테스트를 준비하면서 for 루프 문을 이용하는 방식보다 더 나은 고차 함수 방식에 대해 알아보고 정리해보려 한다.
고차 함수를 이용하게 되면 코드를 더욱 선언적으로, 가독성이 높게 코드를 작성할 수 있다.
자바스크립트에서 **고차 함수(Higher-Order Function)**란 '함수를 인자로 받거나' 또는 '함수를 반환하는' 함수를 말한다. (setTimeout이나 addEventListener처럼 함수를 인자로 받는 많은 함수가 이미 고차 함수에 속한다.)
이 글에서는 고차 함수 중에서도 특히 배열을 다룰 때 for문을 대체하여 유용하게 쓰이는 대표적인 배열 고차 함수들을 중심으로 알아보려 한다.
자주 쓰이는 함수는 some(), every(), find(), filter(), map(), reduce() 등이다.
각각 하나씩 살펴보자.
map() 함수는 배열의 각 요소를 변형하여 새로운 배열을 만들 때 사용한다. (즉, 원본 배열은 유지한다)
배열의 모든 요소에 일관된 작업을 적용 혹은 수행하여 새로운 형태의 배열을 만들고자 할 때 사용하면 된다.
const numbers = [1, 2, 3, 4];
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8]
console.log(numbers); // [1, 2, 3, 4] (원본 배열은 변하지 않습니다)
---
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
const names = users.map((user) => user.name);
console.log(names); // ["Alice", "Bob", "Charlie"]
filter()는 배열의 각 요소를 조건에 대한 검사를 진행하면서 조건에 부합하는 요소만 모아서 새로운 배열을 반환한다.
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter((num) => num % 2 === 0);
console.log(evens); // [2, 4, 6]
---
const students = [
{ name: "Alice", score: 85 },
{ name: "Bob", score: 92 },
{ name: "Charlie", score: 78 },
];
const passStudents = students.filter((student) => student.score >= 90);
console.log(passStudents); // [{ name: "Bob", score: 92 }]
find()는 filter()와 비슷한 조건을 검사하지만, 조건을 통과하는 첫 요소(배열X) 하나만 반환한다.
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
const userBob = users.find((user) => user.id === 2);
console.log(userBob); // { id: 2, name: "Bob" }
const userDavid = users.find((user) => user.id === 4);
console.log(userDavid); // undefined (찾는 값이 없으면 undefined 반환)
reduce()는 배열의 모든 요소를 순회하며 하나의 결과 값으로 누적한다.
주로 배열을 기반으로 하나의 값을 도출해야 할 때 사용한다.
const numbers = [1, 2, 3, 4, 5];
// 0은 초기값(initialValue)입니다.
const sum = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0);
console.log(sum); // 15
// 과정:
// 1. acc: 0, cur: 1 => 1
// 2. acc: 1, cur: 2 => 3
// 3. acc: 3, cur: 3 => 6
// 4. acc: 6, cur: 4 => 10
// 5. acc: 10, cur: 5 => 15
---
const cart = [
{ item: "Laptop", price: 1000 },
{ item: "Mouse", price: 50 },
{ item: "Keyboard", price: 100 },
];
const totalPrice = cart.reduce((total, item) => total + item.price, 0);
console.log(totalPrice); // 1150
some()은 배열의 요소 중 단 하나라도 주어진 조건을 만족하는지 검사한다.
조건에 맞는 요소를 찾는 즉시 순회를 멈추고 true를 반환한다.
const numbers = [1, 2, 3, 4, 5];
// 0은 초기값(initialValue)입니다.
const sum = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0);
console.log(sum); // 15
// 과정:
// 1. acc: 0, cur: 1 => 1
// 2. acc: 1, cur: 2 => 3
// 3. acc: 3, cur: 3 => 6
// 4. acc: 6, cur: 4 => 10
// 5. acc: 10, cur: 5 => 15
---
const cart = [
{ item: "Laptop", price: 1000 },
{ item: "Mouse", price: 50 },
{ item: "Keyboard", price: 100 },
];
const totalPrice = cart.reduce((total, item) => total + item.price, 0);
console.log(totalPrice); // 1150
every()는 some()과 반대로, 배열의 모든 요소가 조건을 만족하는지 검사한다.
조건을 만족하지 못하는 요소를 찾는 즉시 순회를 멈추고 false를 반환한다.
const ages1 = [20, 31, 19, 40];
const allAdults1 = ages1.every((age) => age >= 18);
console.log(allAdults1); // true
const ages2 = [20, 31, 15, 40]; // 15가 있음
const allAdults2 = ages2.every((age) => age >= 18);
console.log(allAdults2); // false (15를 발견하고 즉시 멈춤)
위 6가지 외에도 코딩 테스트나 실무에서 유용한 배열 고차함수들이 더 있다.
forEach()는 map()과 매우 유사하게 배열의 모든 요소를 순회하지만, 새로운 배열을 반환하지 않는다 (undefined 반환). (즉, 원본 배열은 유지한다)\
const names = ["Alice", "Bob", "Charlie"];
// forEach는 반환 값이 없습니다.
const result = names.forEach((name, index) => {
console.log(`${index + 1}번째 이름: ${name}`);
});
// 출력:
// 1번째 이름: Alice
// 2번째 이름: Bob
// 3번째 이름: Charlie
console.log(result); // undefined
findIndex()는 find()와 동일하게 조건을 검사하지만, 조건을 통과하는 첫 번째 요소의 인덱스를 반환한다.
조건을 만족하는 요소가 배열의 몇 번째에 있는지 그 위치가 필요할 때 사용된다.
const users = [
{ id: 1, name: "Alice" },
{ id: 10, name: "Bob" },
{ id: 25, name: "Charlie" },
];
const userBobIndex = users.findIndex((user) => user.name === "Bob");
console.log(userBobIndex); // 1
const userDavidIndex = users.findIndex((user) => user.name === "David");
console.log(userDavidIndex); // -1 (찾는 값이 없으면 -1 반환)
sort()는 배열의 요소를 정렬한다.
이때 정렬 기준을 정의하는 '비교 함수'를 인자로 받기 때문에 고차 함수에 속한다.
sort()는 다른 함수들(map(), filter() 등)과 달리 원본 배열 자체를 수정한다.
코딩 테스트에서 원본 배열을 유지해야 한다면, 반드시 배열을 복사한뒤에 sort()를 적용해야 한다.
const numbers = [4, 1, 10, 3, 20];
// 비교 함수 (a, b) => a - b 를 인자로 전달 (오름차순)
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 3, 4, 10, 20] (😱 원본이 변경됨)
---
// [Tip] 원본을 유지하며 정렬하려면?
const originalNumbers = [4, 1, 10, 3, 20];
// 1. 배열을 복사(전개 연산자 '...')한 뒤 sort
const sortedCopy = [...originalNumbers].sort((a, b) => a - b);
// 2. (최신) ES2023의 toSorted() 사용
// const sortedCopy = originalNumbers.toSorted((a, b) => a - b);
console.log(sortedCopy); // [1, 3, 4, 10, 20]
console.log(originalNumbers); // [4, 1, 10, 3, 20] (원본 유지)
이 함수들은 단독으로 사용되기도 하지만, filter()로 거른 뒤 map()으로 변형하고 reduce()로 합계를 내는 등 연속적으로 사용할 때 for문보다 더욱 강력하고 뛰어난 가독성을 보여준다.
코딩 테스트에서 배열 문제가 나왔을 때 적극적으로 알맞는 함수를 사용하는 것이 좋겠다.