한국어

JavaScript 배열로 함수형 프로그래밍의 힘을 활용하세요. 내장 메서드를 사용하여 데이터를 효율적으로 변환, 필터링, 축약하는 방법을 알아보세요.

JavaScript 배열로 함수형 프로그래밍 마스터하기

끊임없이 진화하는 웹 개발 환경에서 JavaScript는 계속해서 초석이 되고 있습니다. 객체 지향 및 명령형 프로그래밍 패러다임이 오랫동안 지배적이었지만, 함수형 프로그래밍(FP)이 상당한 주목을 받고 있습니다. FP는 불변성, 순수 함수, 선언형 코드를 강조하여 더욱 강력하고 유지 관리하기 쉬우며 예측 가능한 애플리케이션을 만들 수 있습니다. JavaScript에서 함수형 프로그래밍을 가장 강력하게 받아들이는 방법 중 하나는 네이티브 배열 메서드를 활용하는 것입니다.

이 포괄적인 가이드에서는 JavaScript 배열을 사용하여 함수형 프로그래밍 원칙의 힘을 어떻게 활용할 수 있는지 자세히 살펴보겠습니다. 주요 개념을 살펴보고 map, filter, reduce와 같은 메서드를 사용하여 적용하는 방법을 시연하여 데이터 조작 방식을 변화시킬 것입니다.

함수형 프로그래밍이란 무엇인가요?

JavaScript 배열에 들어가기 전에 함수형 프로그래밍에 대해 간략하게 정의해 보겠습니다. 핵심은 FP를 계산을 수학적 함수의 평가로 취급하고 상태 변경 및 가변 데이터를 피하는 프로그래밍 패러다임입니다. 주요 원칙은 다음과 같습니다.

이러한 원칙을 채택하면 특히 복잡한 애플리케이션에서 추론, 테스트 및 디버깅하기 쉬운 코드를 얻을 수 있습니다. JavaScript의 배열 메서드는 이러한 개념을 구현하는 데 완벽하게 적합합니다.

JavaScript 배열 메서드의 힘

JavaScript 배열에는 전통적인 루프(for 또는 while과 같은)에 의존하지 않고 복잡한 데이터 조작을 허용하는 풍부한 내장 메서드 세트가 장착되어 있습니다. 이러한 메서드는 종종 새 배열을 반환하여 불변성을 촉진하고 콜백 함수를 허용하여 함수형 접근 방식을 가능하게 합니다.

가장 기본적인 함수형 배열 메서드를 살펴보겠습니다.

1. Array.prototype.map()

map() 메서드는 호출 배열의 각 요소에 제공된 함수를 호출한 결과로 채워진 새 배열을 만듭니다. 배열의 각 요소를 새로운 것으로 변환하는 데 이상적입니다.

구문:

array.map(callback(currentValue[, index[, array]])[, thisArg])

주요 특징:

예제: 각 숫자 두 배로 만들기

숫자 배열이 있고 각 숫자가 두 배가 된 새 배열을 만들고 싶다고 상상해 보세요.

const numbers = [1, 2, 3, 4, 5];

// 변환을 위한 map 사용
const doubledNumbers = numbers.map(number => number * 2);

console.log(numbers); // 출력: [1, 2, 3, 4, 5] (원본 배열은 변경되지 않음)
console.log(doubledNumbers); // 출력: [2, 4, 6, 8, 10]

예제: 객체에서 속성 추출하기

일반적인 사용 사례는 객체 배열에서 특정 속성을 추출하는 것입니다. 사용자 목록이 있고 이름만 가져오고 싶다고 가정해 봅시다.

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const userNames = users.map(user => user.name);

console.log(userNames); // 출력: ['Alice', 'Bob', 'Charlie']

2. Array.prototype.filter()

filter() 메서드는 제공된 함수에 의해 구현된 테스트를 통과하는 모든 요소를 포함하는 새 배열을 만듭니다. 조건에 따라 요소를 선택하는 데 사용됩니다.

구문:

array.filter(callback(element[, index[, array]])[, thisArg])

주요 특징:

예제: 짝수 필터링

짝수만 유지하도록 숫자 배열을 필터링해 봅시다.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 짝수 선택을 위한 filter 사용
const evenNumbers = numbers.filter(number => number % 2 === 0);

console.log(numbers); // 출력: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(evenNumbers); // 출력: [2, 4, 6, 8, 10]

예제: 활성 사용자 필터링

사용자 배열에서 활성으로 표시된 사용자를 필터링해 봅시다.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const activeUsers = users.filter(user => user.isActive);

console.log(activeUsers); 
/* 출력:
[
  { id: 1, name: 'Alice', isActive: true },
  { id: 3, name: 'Charlie', isActive: true }
]
*/

3. Array.prototype.reduce()

reduce() 메서드는 이전 요소에 대한 계산 반환 값을 전달하여 배열의 각 요소에 대해 사용자 제공 “리듀서” 콜백 함수를 순서대로 실행합니다. 배열의 모든 요소를 통해 리듀서를 실행한 최종 결과는 단일 값입니다.

이것은 아마도 배열 메서드 중에서 가장 다재다능하며 많은 함수형 프로그래밍 패턴의 초석으로, 배열을 단일 값(예: 합계, 곱, 개수 또는 새 객체 또는 배열)으로 “줄이는” 것을 허용합니다.

구문:

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

주요 특징:

예제: 숫자 합산

배열의 모든 숫자를 합산해 봅시다.

const numbers = [1, 2, 3, 4, 5];

// 숫자를 합산하기 위한 reduce 사용
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // 0은 initialValue입니다.

console.log(sum); // 출력: 15

설명:

예제: 속성별로 객체 그룹화

reduce를 사용하여 객체 배열을 특정 속성별로 그룹화된 객체로 변환할 수 있습니다. 사용자들을 `isActive` 상태별로 그룹화해 봅시다.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const groupedUsers = users.reduce((acc, user) => {
  const status = user.isActive ? 'active' : 'inactive';
  if (!acc[status]) {
    acc[status] = [];
  }
  acc[status].push(user);
  return acc;
}, {}); // 빈 객체 {}는 initialValue입니다.

console.log(groupedUsers);
/* 출력:
{
  active: [
    { id: 1, name: 'Alice', isActive: true },
    { id: 3, name: 'Charlie', isActive: true }
  ],
  inactive: [
    { id: 2, name: 'Bob', isActive: false },
    { id: 4, name: 'David', isActive: false }
  ]
}
*/

예제: 발생 횟수 계산

과일 목록에서 각 과일의 빈도를 계산해 봅시다.

const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const fruitCounts = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});

console.log(fruitCounts); // 출력: { apple: 3, banana: 2, orange: 1 }

4. Array.prototype.forEach()

forEach()는 새 배열을 반환하지 않고 종종 더 명령형으로 간주되지만(주 목적은 각 배열 요소에 대해 함수를 실행하는 것이므로), 부작용이 필요하거나 변환된 출력을 필요로 하지 않고 반복할 때 함수형 패턴에 역할을 하는 기본적인 메서드입니다.

구문:

array.forEach(callback(element[, index[, array]])[, thisArg])

주요 특징:

예제: 각 요소 로깅

const messages = ['Hello', 'Functional', 'World'];

messages.forEach(message => console.log(message));
// 출력:
// Hello
// Functional
// World

참고: 변환 및 필터링의 경우 불변성 및 선언적 특성으로 인해 mapfilter가 선호됩니다. 새 구조로 결과를 수집하지 않고 각 항목에 대해 작업을 수행해야 하는 경우 forEach가 더 적합한 선택입니다.

5. Array.prototype.find()Array.prototype.findIndex()

이 메서드는 배열에서 특정 요소를 찾는 데 유용합니다.

예제: 사용자 찾기

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const bob = users.find(user => user.name === 'Bob');
const bobIndex = users.findIndex(user => user.name === 'Bob');
const nonExistentUser = users.find(user => user.name === 'David');
const nonExistentIndex = users.findIndex(user => user.name === 'David');

console.log(bob); // 출력: { id: 2, name: 'Bob' }
console.log(bobIndex); // 출력: 1
console.log(nonExistentUser); // 출력: undefined
console.log(nonExistentIndex); // 출력: -1

6. Array.prototype.some()Array.prototype.every()

이 메서드는 배열의 모든 요소가 제공된 함수에 의해 구현된 테스트를 통과하는지 테스트합니다.

예제: 사용자 상태 확인

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true }
];

const hasInactiveUser = users.some(user => !user.isActive);
const allAreActive = users.every(user => user.isActive);

console.log(hasInactiveUser); // 출력: true (Bob이 비활성이므로)
console.log(allAreActive); // 출력: false (Bob이 비활성이므로)

const allUsersActive = users.filter(user => user.isActive).length === users.length;
console.log(allUsersActive); // 출력: false

// every를 직접 사용하는 대안
const allUsersActiveDirect = users.every(user => user.isActive);
console.log(allUsersActiveDirect); // 출력: false

복잡한 작업을 위한 배열 메서드 체이닝

JavaScript 배열과 함수형 프로그래밍의 진정한 힘은 이러한 메서드를 함께 체이닝할 때 빛을 발합니다. 이러한 메서드의 대부분은 새 배열을 반환하므로(forEach 제외), 한 메서드의 출력을 다른 메서드의 입력으로 원활하게 파이프하여 우아하고 읽기 쉬운 데이터 파이프라인을 만들 수 있습니다.

예제: 활성 사용자 이름 찾기 및 ID 두 배로 만들기

활성 사용자를 모두 찾고 이름을 추출한 다음, 각 이름 앞에 필터링된 목록에서의 인덱스를 나타내는 숫자와 ID가 두 배가 된 새 배열을 만들어 보겠습니다.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: true },
  { id: 5, name: 'Eve', isActive: false }
];

const processedActiveUsers = users
  .filter(user => user.isActive) // 활성 사용자만 가져오기
  .map((user, index) => ({      // 각 활성 사용자 변환
    name: `${index + 1}. ${user.name}`,
    doubledId: user.id * 2
  }));

console.log(processedActiveUsers);
/* 출력:
[
  { name: '1. Alice', doubledId: 2 },
  { name: '2. Charlie', doubledId: 6 },
  { name: '3. David', doubledId: 8 }
]
*/

이 체인 방식은 선언적입니다. 명시적인 루프 관리 없이 단계(필터링 후 매핑)를 지정합니다. 또한 각 단계가 새 배열 또는 객체를 생성하여 원본 users 배열을 그대로 두기 때문에 불변합니다.

불변성 실천

함수형 프로그래밍은 불변성에 크게 의존합니다. 즉, 기존 데이터 구조를 수정하는 대신 원하는 변경 사항으로 새 구조를 만듭니다. map, filter, slice와 같은 JavaScript 배열 메서드는 새 배열을 반환함으로써 본질적으로 이를 지원합니다.

불변성이 왜 중요한가요?

전통적으로 배열을 수정하는 작업(요소 추가 또는 제거와 같은)을 수행해야 할 때 slice, 스프레드 구문(...)과 같은 메서드를 사용하거나 다른 함수형 메서드를 결합하여 불변성을 달성할 수 있습니다.

예제: 불변 방식으로 요소 추가

const originalArray = [1, 2, 3];

// 명령형 방식 (originalArray를 수정함)
// originalArray.push(4);

// 스프레드 구문을 사용한 함수형 방식
const newArrayWithPush = [...originalArray, 4];
console.log(originalArray); // 출력: [1, 2, 3]
console.log(newArrayWithPush); // 출력: [1, 2, 3, 4]

// slice 및 연결을 사용한 함수형 방식 (이제는 덜 일반적임)
const newArrayWithSlice = originalArray.slice(0, originalArray.length).concat(4);
console.log(newArrayWithSlice); // 출력: [1, 2, 3, 4]

예제: 불변 방식으로 요소 제거

const originalArray = [1, 2, 3, 4, 5];

// 인덱스 2 (값 3)의 요소 제거

// slice 및 스프레드 구문을 사용한 함수형 방식
const newArrayAfterSplice = [
  ...originalArray.slice(0, 2),
  ...originalArray.slice(3)
];
console.log(originalArray); // 출력: [1, 2, 3, 4, 5]
console.log(newArrayAfterSplice); // 출력: [1, 2, 4, 5]

// filter를 사용하여 특정 값 제거
const newValueToRemove = 3;
const arrayWithoutValue = originalArray.filter(item => item !== newValueToRemove);
console.log(arrayWithoutValue); // 출력: [1, 2, 4, 5]

모범 사례 및 고급 기법

함수형 배열 메서드에 더 익숙해지면 이러한 모범 사례를 고려해 보세요.

예제: 데이터 집계에 대한 함수형 접근 방식

다른 지역의 판매 데이터가 있고 각 지역별 총 판매액을 계산한 다음 판매액이 가장 많은 지역을 찾고 싶다고 가정해 봅시다.

const salesData = [
  { region: 'North', amount: 100 },
  { region: 'South', amount: 150 },
  { region: 'North', amount: 120 },
  { region: 'East', amount: 200 },
  { region: 'South', amount: 180 },
  { region: 'North', amount: 90 }
];

// 1. reduce를 사용하여 지역별 총 판매액 계산
const salesByRegion = salesData.reduce((acc, sale) => {
  acc[sale.region] = (acc[sale.region] || 0) + sale.amount;
  return acc;
}, {});

// salesByRegion은 다음과 같습니다: { North: 310, South: 330, East: 200 }

// 2. 집계된 객체를 추가 처리를 위해 객체 배열로 변환
const salesArray = Object.keys(salesByRegion).map(region => ({
  region: region,
  totalAmount: salesByRegion[region]
}));

// salesArray는 다음과 같습니다: [
//   { region: 'North', totalAmount: 310 },
//   { region: 'South', totalAmount: 330 },
//   { region: 'East', totalAmount: 200 }
// ]

// 3. reduce를 사용하여 판매액이 가장 많은 지역 찾기
const highestSalesRegion = salesArray.reduce((max, current) => {
  return current.totalAmount > max.totalAmount ? current : max;
}, { region: '', totalAmount: -Infinity }); // 매우 작은 숫자로 초기화

console.log('Sales by Region:', salesByRegion);
console.log('Sales Array:', salesArray);
console.log('Region with Highest Sales:', highestSalesRegion);

/*
출력:
Sales by Region: { North: 310, South: 330, East: 200 }
Sales Array: [
  { region: 'North', totalAmount: 310 },
  { region: 'South', totalAmount: 330 },
  { region: 'East', totalAmount: 200 }
]
Region with Highest Sales: { region: 'South', totalAmount: 330 }
*/

결론

JavaScript 배열을 사용한 함수형 프로그래밍은 단순한 스타일 선택이 아니라 더 깨끗하고 예측 가능하며 강력한 코드를 작성하는 강력한 방법입니다. map, filter, reduce와 같은 메서드를 활용함으로써 불변성 및 순수 함수와 같은 함수형 프로그래밍의 핵심 원칙을 준수하면서 데이터를 효과적으로 변환, 쿼리 및 집계할 수 있습니다.

JavaScript 개발 여정을 계속하면서 이러한 함수형 패턴을 일상적인 워크플로에 통합하면 의심할 여지 없이 더 유지 관리 가능하고 확장 가능한 애플리케이션을 얻을 수 있습니다. 프로젝트에서 이러한 배열 메서드를 실험하는 것부터 시작하면 곧 그 엄청난 가치를 발견하게 될 것입니다.