자바스크립트: 일반적인 방식 vs 함수형 프로그래밍 비교 코드

1. 배열 처리: 데이터 변환

일반적인 방식 (명령형)

// 숫자 배열을 2배로 만들기
const numbers = [1, 2, 3, 4, 5];
const doubled = [];
 
for (let i = 0; i < numbers.length; i++) {
    doubled.push(numbers[i] * 2);
}
 
console.log(doubled); // [2, 4, 6, 8, 10]

함수형 방식 (선언형)

// 숫자 배열을 2배로 만들기
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(x => x * 2);
 
console.log(doubled); // [2, 4, 6, 8, 10]

차이점12:

  • 명령형: “어떻게” 할지 단계별로 명시 (반복, 인덱스 관리, push)
  • 함수형: “무엇을” 할지 선언 (map으로 변환 의도 명확)

2. 배열 필터링: 조건부 선택

일반적인 방식

// 짝수만 선택하기
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = [];
 
for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 === 0) {
        evens.push(numbers[i]);
    }
}
 
console.log(evens); // [2, 4, 6, 8, 10]

함수형 방식

// 짝수만 선택하기
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = numbers.filter(x => x % 2 === 0);
 
console.log(evens); // [2, 4, 6, 8, 10]

차이점34:

  • 명령형: 루프와 조건문으로 단계별 구현
  • 함수형: filter로 의도를 직접적으로 표현

3. 배열 집계: 합계 계산

일반적인 방식

// 배열의 모든 수 더하기
const numbers = [1, 2, 3, 4, 5];
let sum = 0;
 
for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
}
 
console.log(sum); // 15

함수형 방식

// 배열의 모든 수 더하기
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
 
console.log(sum); // 15

차이점24:

  • 명령형: 가변 변수(sum)를 직접 조작
  • 함수형: reduce로 축약 로직을 표현, 초기값 명시

4. 복합 데이터 처리: 체이닝

일반적인 방식

// 짝수를 찾아서 2배로 만든 후 합계 구하기
const numbers = [1, 2, 3, 4, 5, 6];
const evens = [];
let sum = 0;
 
// 1단계: 짝수 필터링
for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 === 0) {
        evens.push(numbers[i]);
    }
}
 
// 2단계: 2배로 만들기
const doubled = [];
for (let i = 0; i < evens.length; i++) {
    doubled.push(evens[i] * 2);
}
 
// 3단계: 합계 구하기
for (let i = 0; i < doubled.length; i++) {
    sum += doubled[i];
}
 
console.log(sum); // 24 (2*2 + 4*2 + 6*2)

함수형 방식

// 짝수를 찾아서 2배로 만든 후 합계 구하기
const numbers = [1, 2, 3, 4, 5, 6];
const sum = numbers
    .filter(x => x % 2 === 0)  // 짝수 선택
    .map(x => x * 2)           // 2배로 만들기
    .reduce((acc, curr) => acc + curr, 0); // 합계
 
console.log(sum); // 24

차이점53:

  • 명령형: 단계별로 중간 배열 생성, 3개의 반복문
  • 함수형: 메서드 체이닝으로 파이프라인 구성

5. 객체 데이터 처리

일반적인 방식

// 사용자 목록에서 성인만 선택해서 이름 추출
const users = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 17 },
    { name: 'Charlie', age: 30 },
    { name: 'David', age: 16 }
];
 
const adultNames = [];
for (let i = 0; i < users.length; i++) {
    if (users[i].age >= 18) {
        adultNames.push(users[i].name);
    }
}
 
console.log(adultNames); // ['Alice', 'Charlie']

함수형 방식

// 사용자 목록에서 성인만 선택해서 이름 추출
const users = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 17 },
    { name: 'Charlie', age: 30 },
    { name: 'David', age: 16 }
];
 
const adultNames = users
    .filter(user => user.age >= 18)
    .map(user => user.name);
 
console.log(adultNames); // ['Alice', 'Charlie']

6. 상태 변경: 가변성 vs 불변성

일반적인 방식 (가변 데이터)

// 사용자 정보 업데이트 (원본 수정)
let user = { name: 'John', age: 25, city: 'Seoul' };
 
function updateUserAge(user, newAge) {
    user.age = newAge; // 원본 객체 직접 수정
    return user;
}
 
function updateUserCity(user, newCity) {
    user.city = newCity; // 원본 객체 직접 수정
    return user;
}
 
console.log('원본:', user);
updateUserAge(user, 26);
console.log('나이 변경 후:', user); // 원본이 변경됨
updateUserCity(user, 'Busan');
console.log('도시 변경 후:', user); // 원본이 또 변경됨

함수형 방식 (불변 데이터)

// 사용자 정보 업데이트 (새 객체 생성)
const user = { name: 'John', age: 25, city: 'Seoul' };
 
const updateUserAge = (user, newAge) => ({
    ...user,
    age: newAge // 새 객체 생성
});
 
const updateUserCity = (user, newCity) => ({
    ...user,
    city: newCity // 새 객체 생성
});
 
console.log('원본:', user);
const userWithNewAge = updateUserAge(user, 26);
console.log('나이 변경 후 새 객체:', userWithNewAge);
console.log('원본 유지:', user); // 원본 그대로
 
const userWithNewCity = updateUserCity(userWithNewAge, 'Busan');
console.log('최종:', userWithNewCity);

차이점678:

  • 명령형: 원본 데이터 직접 수정 (가변성)
  • 함수형: 스프레드 연산자로 새 객체 생성 (불변성)

7. 부작용(Side Effects) 처리

일반적인 방식 (부작용 있음)

// 전역 변수에 의존하는 비순수 함수
let counter = 0;
 
function processItems(items) {
    const results = [];
    
    for (let i = 0; i < items.length; i++) {
        counter++; // 외부 상태 변경 (부작용)
        console.log(`처리 중: ${items[i]}`); // 로그 출력 (부작용)
        results.push(items[i].toUpperCase());
    }
    
    return results;
}
 
const items = ['apple', 'banana', 'cherry'];
console.log(processItems(items));
console.log('처리된 항목 수:', counter); // 전역 상태에 의존

함수형 방식 (순수 함수)

// 순수 함수로 부작용 분리
const processItems = (items) => 
    items.map(item => item.toUpperCase());
 
const logProgress = (item, index) => 
    console.log(`처리 중 ${index + 1}: ${item}`);
 
const countItems = (items) => items.length;
 
// 사용
const items = ['apple', 'banana', 'cherry'];
 
// 부작용이 있는 작업은 별도로 처리
items.forEach(logProgress);
 
// 순수 함수로 데이터 변환
const results = processItems(items);
console.log(results);
 
// 순수 함수로 카운트
const count = countItems(items);
console.log('처리된 항목 수:', count);

차이점91011:

  • 명령형: 함수 내에서 외부 상태 변경과 로그 출력
  • 함수형: 순수 함수와 부작용을 분리

8. 조건부 로직 처리

일반적인 방식

// 사용자 등급별 할인율 적용
const users = [
    { name: 'Alice', type: 'premium', purchaseAmount: 1000 },
    { name: 'Bob', type: 'regular', purchaseAmount: 500 },
    { name: 'Charlie', type: 'premium', purchaseAmount: 1500 }
];
 
const processedUsers = [];
 
for (let i = 0; i < users.length; i++) {
    let discount = 0;
    let finalAmount = users[i].purchaseAmount;
    
    if (users[i].type === 'premium') {
        discount = 0.2; // 20% 할인
    } else if (users[i].type === 'regular') {
        discount = 0.1; // 10% 할인
    }
    
    finalAmount = users[i].purchaseAmount * (1 - discount);
    
    processedUsers.push({
        ...users[i],
        discount: discount,
        finalAmount: finalAmount
    });
}
 
console.log(processedUsers);

함수형 방식

// 사용자 등급별 할인율 적용
const users = [
    { name: 'Alice', type: 'premium', purchaseAmount: 1000 },
    { name: 'Bob', type: 'regular', purchaseAmount: 500 },
    { name: 'Charlie', type: 'premium', purchaseAmount: 1500 }
];
 
const getDiscount = (userType) => {
    const discounts = {
        premium: 0.2,
        regular: 0.1,
        default: 0
    };
    return discounts[userType] || discounts.default;
};
 
const applyDiscount = (user) => {
    const discount = getDiscount(user.type);
    const finalAmount = user.purchaseAmount * (1 - discount);
    
    return {
        ...user,
        discount,
        finalAmount
    };
};
 
const processedUsers = users.map(applyDiscount);
console.log(processedUsers);

9. 에러 처리

일반적인 방식

// 나이 검증 및 처리
function processUsers(users) {
    const adults = [];
    const errors = [];
    
    for (let i = 0; i < users.length; i++) {
        try {
            if (typeof users[i].age !== 'number' || users[i].age < 0) {
                throw new Error(`Invalid age for ${users[i].name}`);
            }
            
            if (users[i].age >= 18) {
                adults.push({
                    ...users[i],
                    status: 'adult'
                });
            }
        } catch (error) {
            errors.push(error.message);
        }
    }
    
    return { adults, errors };
}

함수형 방식

// 나이 검증 및 처리
const isValidAge = (age) => 
    typeof age === 'number' && age >= 0;
 
const isAdult = (age) => age >= 18;
 
const validateUser = (user) => 
    isValidAge(user.age) 
        ? { success: true, data: user }
        : { success: false, error: `Invalid age for ${user.name}` };
 
const processAdult = (user) => 
    isAdult(user.age) 
        ? { ...user, status: 'adult' }
        : null;
 
const processUsers = (users) => {
    const validations = users.map(validateUser);
    
    const errors = validations
        .filter(v => !v.success)
        .map(v => v.error);
    
    const adults = validations
        .filter(v => v.success)
        .map(v => processAdult(v.data))
        .filter(Boolean);
    
    return { adults, errors };
};

10. 성능 비교

성능 테스트 예제

// 성능 테스트용 큰 배열
const largeArray = Array.from({ length: 1000000 }, (_, i) => i);
 
// 명령형 방식 성능 측정
console.time('명령형');
const imperativeResult = [];
for (let i = 0; i < largeArray.length; i++) {
    if (largeArray[i] % 2 === 0) {
        imperativeResult.push(largeArray[i] * 2);
    }
}
console.timeEnd('명령형');
 
// 함수형 방식 성능 측정
console.time('함수형');
const functionalResult = largeArray
    .filter(x => x % 2 === 0)
    .map(x => x * 2);
console.timeEnd('함수형');
 
console.log('결과 길이 동일:', imperativeResult.length === functionalResult.length);

성능 특성112:

  • 명령형: 일반적으로 더 빠름 (단일 루프, 메서드 호출 오버헤드 없음)
  • 함수형: 가독성이 좋지만 중간 배열 생성으로 메모리 사용량 증가

결론

측면일반적인 방식 (명령형)함수형 방식
가독성단계별 구현으로 복잡함의도가 명확하고 선언적
재사용성특정 상황에 특화됨작은 함수 조합으로 재사용 용이
디버깅중간 상태 확인 가능순수 함수로 예측 가능
성능일반적으로 더 빠름중간 배열 생성으로 오버헤드
메모리효율적불변성으로 메모리 사용량 증가
테스트상태 의존성으로 복잡격리된 순수 함수로 간단

실무 권장사항:

  • 성능이 중요한 대용량 데이터: 명령형 방식
  • 비즈니스 로직, 데이터 변환: 함수형 방식
  • 복잡한 상태 관리: 적절한 혼합 사용

두 방식 모두 상황에 따른 장단점이 있으므로, 문제의 특성과 요구사항에 맞는 선택이 중요합니다.

Footnotes

  1. https://www.manuelsanchezdev.com/blog/functional-vs-imperative-javascript-performance 2

  2. https://flaviocopes.com/javascript-loops-map-filter-reduce-find/ 2

  3. https://tgdwyer.github.io/functionaljavascript/ 2

  4. https://dev.to/joelnet/map-filter-reduce-vs-for-loops-syntax-2k5l 2

  5. https://codeburst.io/examples-in-javascript-functional-programming-part-1-c9e2df8a411a

  6. https://www.linkedin.com/pulse/javascript-mutable-immutable-bharath-kumar-murugan

  7. https://corner.buka.sh/understanding-mutable-and-immutable-data-in-javascript-a-beginners-guide/

  8. https://www.freecodecamp.org/news/mutability-vs-immutability-in-javascript/

  9. https://jrsinclair.com/articles/2018/how-to-deal-with-dirty-side-effects-in-your-pure-functional-javascript/

  10. https://www.freecodecamp.org/news/pure-function-vs-impure-function/

  11. https://dev.to/vonheikemen/dealing-with-side-effects-and-pure-functions-in-javascript-16mg

  12. https://www.reddit.com/r/javascript/comments/4nvcd5/performancewise_is_the_for_loop_better_than_map/