Develop

현대 JavaScript 마스터하기: ES6+와 함수형 프로그래밍의 완벽 가이드

issuemaker99 2025. 3. 26. 10:55
728x90

소개: 왜 지금 최신 JavaScript를 배워야 하는가?

2015년 ES6(ECMAScript 2015)의 등장은 JavaScript 역사에서 가장 혁명적인 변화였습니다. 그 이후로도 매년 새로운 기능이 추가되면서 JavaScript는 더욱 강력하고 표현력이 풍부한 언어로 발전해왔습니다. 하지만 여전히 많은 개발자들이 이전 방식의 코딩에 머물러 있습니다.

이 글을 읽어야 하는 이유:

  • 💼 취업 시장에서 경쟁력 확보 (최신 JS 기술은 필수 요구사항)
  • 🚀 생산성 30% 이상 향상 가능
  • 🔍 가독성과 유지보수성이 뛰어난 코드 작성 능력 획득
  • 🛡️ 더 적은 버그와 예상치 못한 동작 방지

자, 이제 현대 JavaScript의 세계로 뛰어들어 봅시다!

ES6+ 핵심 문법 총정리

1. 화살표 함수: 간결함의 극치

// 기존 방식
function add(a, b) {
  return a + b;
}

// 화살표 함수
const add = (a, b) => a + b;

// 본문이 여러 줄일 때
const calculate = (a, b) => {
  const result = a * b;
  return result + 5;
};

💡 Pro Tip: 화살표 함수는 this 바인딩을 상속받기 때문에 메서드나 생성자로 사용하지 않는 것이 좋습니다.

2. 구조 분해 할당: 깔끔한 데이터 추출

// 객체 구조 분해
const person = { name: '김개발', age: 28, job: '프론트엔드 개발자' };
const { name, age } = person;
console.log(name); // '김개발'

// 중첩 객체 구조 분해
const user = {
  id: 1,
  details: {
    firstName: '지훈',
    lastName: '박'
  }
};
const { details: { firstName } } = user;
console.log(firstName); // '지훈'

// 배열 구조 분해
const colors = ['빨강', '파랑', '녹색'];
const [red, blue] = colors;
console.log(red, blue); // '빨강' '파랑'

// 기본값 설정
const [first = '기본값', second] = [];
console.log(first); // '기본값'

3. 스프레드 연산자와 레스트 파라미터

// 배열 합치기
const fruits = ['사과', '바나나'];
const moreFruits = [...fruits, '오렌지', '망고'];
console.log(moreFruits); // ['사과', '바나나', '오렌지', '망고']

// 객체 복사 및 확장
const defaultSettings = { theme: 'dark', fontSize: 16 };
const userSettings = { ...defaultSettings, fontSize: 18 };
console.log(userSettings); // { theme: 'dark', fontSize: 18 }

// 함수에서 레스트 파라미터 사용
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

4. 템플릿 리터럴: 문자열 조작의 혁명

const name = '김코딩';
const job = '개발자';
const age = 28;

// 기존 방식
const greeting = name + '님은 ' + age + '세 ' + job + '입니다.';

// 템플릿 리터럴
const betterGreeting = `${name}님은 ${age}세 ${job}입니다.`;

// 여러 줄 문자열도 쉽게 작성
const multiLine = `
  안녕하세요,
  ${name}님!
  환영합니다.
`;

5. 모듈 시스템: 코드 구조화의 핵심

// math.js - 내보내기
export const PI = 3.14159;
export function square(x) { return x * x; }
export default class Calculator { /* ... */ }

// app.js - 가져오기
import Calculator, { PI, square } from './math.js';
import * as MathUtils from './math.js';

console.log(PI); // 3.14159
console.log(square(4)); // 16

6. 클래스: 객체 지향 프로그래밍의 간결한 문법

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  sayHello() {
    return `안녕하세요, ${this.name}입니다.`;
  }
  
  // 정적 메서드
  static create(name, age) {
    return new Person(name, age);
  }
  
  // getter와 setter
  get profile() {
    return `${this.name}, ${this.age}세`;
  }
  
  set profile(value) {
    [this.name, this.age] = value.split(', ');
  }
}

// 상속
class Employee extends Person {
  constructor(name, age, position) {
    super(name, age);
    this.position = position;
  }
  
  sayHello() {
    return `${super.sayHello()} ${this.position}로 일하고 있습니다.`;
  }
}

함수형 프로그래밍: 미래를 위한 패러다임

함수형 프로그래밍은 단순한 문법적 편의를 넘어서 코드를 바라보는 전혀 다른 관점을 제시합니다. 복잡성을 관리하고 버그를 줄이는 이 접근법은 JavaScript에서 특히 강력합니다.

1. 순수 함수: 예측 가능성의 기반

// ❌ 순수하지 않은 함수 - 외부 상태에 의존
let total = 0;
function addToTotal(value) {
  total += value; // 부작용: 외부 변수 변경
  return total;
}

// ✅ 순수 함수 - 동일 입력, 동일 출력, 부작용 없음
function add(a, b) {
  return a + b;
}

순수 함수의 장점:

  • 테스트 용이성
  • 예측 가능한 결과
  • 병렬 처리 가능
  • 캐싱 최적화 가능

2. 불변성: 안전한 상태 관리

// ❌ 변경 가능한 접근법
function addItem(cart, item) {
  cart.push(item); // 원본 배열 수정
  return cart;
}

// ✅ 불변 접근법
function addItem(cart, item) {
  return [...cart, item]; // 새 배열 반환
}

// 객체 불변 업데이트
function updateUser(user, props) {
  return { ...user, ...props };
}

const user = { name: '김철수', age: 30 };
const updatedUser = updateUser(user, { age: 31 });
// user는 변경되지 않고, updatedUser는 새 객체

3. 고차 함수: 추상화의 마법

// 함수를 인자로 받는 함수
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);

// 함수를 반환하는 함수
function multiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

// 실용적인 예: 디바운스 함수
function debounce(fn, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn.apply(this, args), delay);
  };
}

const debouncedSearch = debounce(searchAPI, 300);

4. 함수 합성: 작은 조각에서 복잡한 기능 구축

// 함수 합성 유틸리티
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);

// 작은 함수들
const addTax = price => price * 1.1;
const formatPrice = price => `₩${price.toFixed(2)}`;
const discount = rate => price => price * (1 - rate);

// 함수들을 조합하여 새로운 함수 생성
const calculateFinalPrice = pipe(
  discount(0.1), // 10% 할인 적용
  addTax,        // 세금 추가
  formatPrice    // 가격 형식화
);

console.log(calculateFinalPrice(1000)); // "₩990.00"

실전 예제: 코드 Before & After

아래 예제를 통해 최신 JavaScript와 함수형 프로그래밍이 실제 코드를 얼마나 개선할 수 있는지 확인해 보세요.

사용자 데이터 처리 예제

Before (ES5 + 명령형):

function getUserData(users) {
  var result = [];
  for (var i = 0; i < users.length; i++) {
    var user = users[i];
    if (user.active && user.age >= 18) {
      var userData = {
        name: user.firstName + ' ' + user.lastName,
        email: user.email
      };
      result.push(userData);
    }
  }
  
  // 이름으로 정렬
  result.sort(function(a, b) {
    if (a.name < b.name) return -1;
    if (a.name > b.name) return 1;
    return 0;
  });
  
  return result;
}

After (ES6+ + 함수형):

const getUserData = (users) => users
  .filter(({ active, age }) => active && age >= 18)
  .map(({ firstName, lastName, email }) => ({
    name: `${firstName} ${lastName}`,
    email
  }))
  .sort((a, b) => a.name.localeCompare(b.name));

비동기 데이터 처리

Before (콜백 지옥):

function loadUserData(userId, callback) {
  getUser(userId, function(error, user) {
    if (error) {
      callback(error);
      return;
    }
    
    getFriends(user.id, function(error, friends) {
      if (error) {
        callback(error);
        return;
      }
      
      getPhotos(user.id, function(error, photos) {
        if (error) {
          callback(error);
          return;
        }
        
        callback(null, {
          user: user,
          friends: friends,
          photos: photos
        });
      });
    });
  });
}

After (async/await + 구조 분해 할당):

async function loadUserData(userId) {
  try {
    const user = await getUser(userId);
    const [friends, photos] = await Promise.all([
      getFriends(user.id),
      getPhotos(user.id)
    ]);
    
    return { user, friends, photos };
  } catch (error) {
    console.error('데이터 로딩 중 오류:', error);
    throw error;
  }
}

성능 최적화 팁

현대 JavaScript 기능을 사용하면서도 성능을 최적화하는 방법을 알아봅시다:

  1. 구조 분해 할당 최적화:
  2. // 깊은 중첩 객체에서 자주 접근하는 프로퍼티만 추출 const { user: { profile: { firstName, lastName } } } = response;
  3. 스프레드 연산자 vs Object.assign:
  4. // 작은 객체는 스프레드 연산자가 더 읽기 쉽고 직관적 const newObj = { ...obj, newProp: value }; // 많은 속성을 가진 큰 객체는 Object.assign이 약간 더 빠를 수 있음 const bigNewObj = Object.assign({}, bigObj, { newProp: value });
  5. 배열 메서드 체이닝 최적화:
  6. // ❌ 비효율적: 각 메서드마다 새 배열 생성 const result = numbers .filter(n => n % 2 === 0) .map(n => n * 2) .reduce((sum, n) => sum + n, 0); // ✅ 효율적: 한 번의 순회로 모든 작업 수행 const result = numbers.reduce((sum, n) => { if (n % 2 === 0) { return sum + (n * 2); } return sum; }, 0);
  7. 메모이제이션으로 계산 캐싱:
  8. function memoize(fn) { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn(...args); cache.set(key, result); return result; }; } // 피보나치 수열 계산 최적화 const fibonacci = memoize(n => { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); });

결론 및 다음 단계

최신 JavaScript 문법과 함수형 프로그래밍의 원칙을 익히는 것은 단순히 트렌드를 따르는 것이 아닌, 더 나은 코드를 작성하기 위한 필수 과정입니다. 이 두 가지를 함께 활용하면:

  • 🧩 코드의 복잡성을 효과적으로 관리할 수 있습니다
  • 🐞 버그가 발생할 확률을 크게 줄일 수 있습니다
  • 📚 코드베이스의 가독성과 유지보수성이 향상됩니다
  • 🚀 개발 속도와 생산성이 높아집니다

 

LIST