JavaScript/함수 완전 정복

JavaScript 화살표 함수 (Arrow Function) 완전 가이드

코딩하는 패션이과생 2025. 6. 7. 08:58
반응형

화살표 함수란?

화살표 함수(Arrow Function)는 ES6(ES2015)에서 도입된 함수 표현식의 간결한 문법입니다. => 기호를 사용하여 함수를 정의하며, 기존 함수 표현식보다 짧고 읽기 쉬운 코드를 작성할 수 있습니다. 특히 콜백 함수나 간단한 함수를 작성할 때 매우 유용합니다.

🔍 왜 화살표 함수가 필요할까?

// ES5 이전 방식 (길고 복잡)
let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.map(function(num) {
    return num * 2;
});

// ES6 화살표 함수 (간결)
let doubledArrow = numbers.map(num => num * 2);

console.log(doubled);      // [2, 4, 6, 8, 10]
console.log(doubledArrow); // [2, 4, 6, 8, 10]

화살표 함수 기본 문법

화살표 함수는 매개변수의 개수와 함수 본문의 복잡도에 따라 다양한 형태로 작성할 수 있습니다.

기본 문법 구조

// 기본 형태
(매개변수) => { 실행문 }

// 매개변수가 1개일 때 괄호 생략 가능
매개변수 => { 실행문 }

// 실행문이 return문 하나일 때 중괄호와 return 생략 가능
매개변수 => 표현식

1단계: 매개변수가 없는 경우

// 일반 함수
function sayHello() {
    return "안녕하세요!";
}

// 화살표 함수
let sayHelloArrow = () => {
    return "안녕하세요!";
};

// 더 간단한 화살표 함수
let sayHelloSimple = () => "안녕하세요!";

console.log(sayHello());        // "안녕하세요!"
console.log(sayHelloArrow());   // "안녕하세요!"
console.log(sayHelloSimple());  // "안녕하세요!"

실행 결과:

안녕하세요!
안녕하세요!
안녕하세요!

2단계: 매개변수가 1개인 경우

// 일반 함수
function square(x) {
    return x * x;
}

// 화살표 함수 (괄호 있음)
let squareArrow1 = (x) => {
    return x * x;
};

// 화살표 함수 (괄호 생략)
let squareArrow2 = x => {
    return x * x;
};

// 가장 간단한 형태
let squareSimple = x => x * x;

console.log(square(5));        // 25
console.log(squareArrow1(5));  // 25
console.log(squareArrow2(5));  // 25
console.log(squareSimple(5));  // 25

실행 결과:

25
25
25
25

3단계: 매개변수가 여러 개인 경우

// 일반 함수
function add(a, b) {
    return a + b;
}

// 화살표 함수 (중괄호 있음)
let addArrow = (a, b) => {
    return a + b;
};

// 간단한 화살표 함수
let addSimple = (a, b) => a + b;

// 복잡한 로직이 있는 화살표 함수
let calculate = (a, b, operation) => {
    console.log(`계산 중: ${a} ${operation} ${b}`);

    if (operation === '+') {
        return a + b;
    } else if (operation === '*') {
        return a * b;
    } else {
        return 0;
    }
};

console.log(add(3, 4));              // 7
console.log(addArrow(3, 4));         // 7
console.log(addSimple(3, 4));        // 7
console.log(calculate(5, 3, '+'));   // 8

실행 결과:

7
7
7
계산 중: 5 + 3
8

4단계: 객체 반환하기

// 객체를 반환할 때는 괄호로 감싸야 함
let createUser = (name, age) => ({
    name: name,
    age: age,
    isAdult: age >= 18
});

// ES6 단축 속성 사용
let createUserShort = (name, age) => ({
    name,
    age,
    isAdult: age >= 18
});

console.log(createUser("김철수", 25));
console.log(createUserShort("이영희", 17));

실행 결과:

{name: "김철수", age: 25, isAdult: true}
{name: "이영희", age: 17, isAdult: false}

일반 함수와의 차이점

화살표 함수와 일반 함수는 문법적 차이 외에도 동작 방식에서 중요한 차이점들이 있습니다.

구문 비교

// 일반 함수 표현식
let normalFunction = function(x, y) {
    return x + y;
};

// 화살표 함수
let arrowFunction = (x, y) => x + y;

// 배열 메서드에서의 사용 비교
let numbers = [1, 2, 3, 4, 5];

// 일반 함수 사용
let doubled1 = numbers.map(function(num) {
    return num * 2;
});

// 화살표 함수 사용
let doubled2 = numbers.map(num => num * 2);

console.log(doubled1); // [2, 4, 6, 8, 10]
console.log(doubled2); // [2, 4, 6, 8, 10]

호이스팅의 차이

// 일반 함수 선언식은 호이스팅됨
console.log(normalDeclared(5)); // 25 (정상 작동)

function normalDeclared(x) {
    return x * x;
}

// 화살표 함수는 변수 호이스팅 규칙을 따름
console.log(arrowFunc(5)); // TypeError: arrowFunc is not a function

let arrowFunc = x => x * x;

arguments 객체

// 일반 함수는 arguments 객체 사용 가능
function normalWithArguments() {
    console.log("arguments 개수:", arguments.length);
    console.log("첫 번째 인수:", arguments[0]);
}

// 화살표 함수는 arguments 객체 없음
let arrowWithoutArguments = () => {
    console.log(arguments); // ReferenceError: arguments is not defined
};

// 화살표 함수에서는 나머지 매개변수 사용
let arrowWithRest = (...args) => {
    console.log("인수 개수:", args.length);
    console.log("첫 번째 인수:", args[0]);
};

normalWithArguments(1, 2, 3);     // 정상 작동
arrowWithRest(1, 2, 3);           // 정상 작동
// arrowWithoutArguments(1, 2, 3); // 에러 발생

실행 결과:

arguments 개수: 3
첫 번째 인수: 1
인수 개수: 3
첫 번째 인수: 1

this 바인딩의 차이

가장 중요한 차이점은 this 바인딩입니다. 이는 화살표 함수 사용 시 가장 주의해야 할 부분입니다.

일반 함수의 this

let person = {
    name: "김철수",
    age: 25,

    // 일반 함수 메서드
    introduce: function() {
        console.log(`안녕하세요, 저는 ${this.name}이고 ${this.age}살입니다.`);
    },

    // 화살표 함수 메서드
    introduceArrow: () => {
        console.log(`안녕하세요, 저는 ${this.name}이고 ${this.age}살입니다.`);
    }
};

person.introduce();      // "안녕하세요, 저는 김철수이고 25살입니다."
person.introduceArrow(); // "안녕하세요, 저는 undefined이고 undefined살입니다."

실행 결과:

안녕하세요, 저는 김철수이고 25살입니다.
안녕하세요, 저는 undefined이고 undefined살입니다.

콜백 함수에서의 this 차이

class Timer {
    constructor(name) {
        this.name = name;
        this.seconds = 0;
    }

    // 일반 함수 사용 (문제 발생)
    startWithNormalFunction() {
        setInterval(function() {
            this.seconds++; // this가 전역 객체를 가리킴
            console.log(`${this.name}: ${this.seconds}초`);
        }, 1000);
    }

    // 화살표 함수 사용 (정상 작동)
    startWithArrowFunction() {
        setInterval(() => {
            this.seconds++; // this가 Timer 인스턴스를 가리킴
            console.log(`${this.name}: ${this.seconds}초`);
        }, 1000);
    }

    // bind를 사용한 해결 방법
    startWithBind() {
        setInterval(function() {
            this.seconds++;
            console.log(`${this.name}: ${this.seconds}초`);
        }.bind(this), 1000);
    }
}

let timer1 = new Timer("타이머1");
timer1.startWithArrowFunction(); // 정상 작동

이벤트 핸들러에서의 this

// HTML: <button id="myButton">클릭하세요</button>

let button = document.getElementById('myButton');
let counter = {
    count: 0,

    // 일반 함수: this가 button 요소를 가리킴
    handleClickNormal: function() {
        console.log(this); // button 요소
        this.count++; // 작동하지 않음 (button.count++)
    },

    // 화살표 함수: this가 counter 객체를 가리킴
    handleClickArrow: () => {
        console.log(this); // window 객체 (전역)
        this.count++; // 작동하지 않음
    },

    // 올바른 방법: 일반 함수 + bind 또는 화살표 함수를 wrapper로 사용
    setupEventListener: function() {
        if (button) {
            button.addEventListener('click', () => {
                this.count++;
                console.log(`클릭 횟수: ${this.count}`);
            });
        }
    }
};

counter.setupEventListener();

언제 사용하고 언제 피해야 할까?

✅ 화살표 함수를 사용하기 좋은 경우

1. 배열 메서드 콜백

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

// map, filter, reduce 등에서 매우 유용
let doubled = numbers.map(n => n * 2);
let evens = numbers.filter(n => n % 2 === 0);
let sum = numbers.reduce((acc, n) => acc + n, 0);

console.log(doubled); // [2, 4, 6, 8, 10]
console.log(evens);   // [2, 4]
console.log(sum);     // 15

2. 간단한 함수 표현식

// 간단한 계산 함수들
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
const isEven = n => n % 2 === 0;
const getFullName = (first, last) => `${first} ${last}`;

// 사용 예제
console.log(add(5, 3));                    // 8
console.log(multiply(4, 7));               // 28
console.log(isEven(6));                    // true
console.log(getFullName("김", "철수"));      // "김 철수"

3. Promise와 비동기 처리

// Promise 체이닝에서 매우 깔끔
fetch('/api/users')
    .then(response => response.json())
    .then(data => data.filter(user => user.active))
    .then(activeUsers => activeUsers.map(user => user.name))
    .then(names => console.log('활성 사용자:', names))
    .catch(error => console.error('에러:', error));

// async/await와 함께
const fetchUserData = async (userId) => {
    try {
        const response = await fetch(`/api/users/${userId}`);
        const user = await response.json();
        return user;
    } catch (error) {
        console.error('사용자 데이터 로드 실패:', error);
        return null;
    }
};

❌ 화살표 함수를 피해야 하는 경우

1. 객체 메서드

// ❌ 나쁜 예: 화살표 함수
let person = {
    name: "김철수",
    greet: () => {
        console.log(`안녕하세요, ${this.name}님!`); // this가 작동하지 않음
    }
};

// ✅ 좋은 예: 일반 함수
let person2 = {
    name: "이영희",
    greet: function() {
        console.log(`안녕하세요, ${this.name}님!`);
    }
};

// ✅ ES6 메서드 축약
let person3 = {
    name: "박민수",
    greet() {
        console.log(`안녕하세요, ${this.name}님!`);
    }
};

2. 생성자 함수

// ❌ 화살표 함수는 생성자로 사용할 수 없음
let ArrowConstructor = (name) => {
    this.name = name;
};

// new ArrowConstructor("테스트"); // TypeError: ArrowConstructor is not a constructor

// ✅ 일반 함수 또는 클래스 사용
function Person(name) {
    this.name = name;
}

// 또는 ES6 클래스
class PersonClass {
    constructor(name) {
        this.name = name;
    }
}

3. 프로토타입 메서드

// ❌ 나쁜 예
Person.prototype.sayHello = () => {
    console.log(`안녕하세요, ${this.name}님!`); // this가 작동하지 않음
};

// ✅ 좋은 예
Person.prototype.sayHello = function() {
    console.log(`안녕하세요, ${this.name}님!`);
};

실무 활용 패턴

🔄 함수형 프로그래밍 패턴

// 데이터 변환 파이프라인
const users = [
    { name: "김철수", age: 25, department: "개발", salary: 50000 },
    { name: "이영희", age: 30, department: "디자인", salary: 55000 },
    { name: "박민수", age: 35, department: "개발", salary: 60000 },
    { name: "최지영", age: 28, department: "마케팅", salary: 45000 }
];

// 개발팀 직원들의 평균 급여 계산
const avgDevSalary = users
    .filter(user => user.department === "개발")
    .map(user => user.salary)
    .reduce((sum, salary) => sum + salary, 0) / 
    users.filter(user => user.department === "개발").length;

console.log(`개발팀 평균 급여: ${avgDevSalary}`); // 55000

// 함수 조합을 위한 헬퍼 함수들
const pipe = (...functions) => value => 
    functions.reduce((acc, fn) => fn(acc), value);

const filter = predicate => array => array.filter(predicate);
const map = transform => array => array.map(transform);
const reduce = (reducer, initial) => array => array.reduce(reducer, initial);

// 사용 예제
const processUsers = pipe(
    filter(user => user.age >= 30),
    map(user => ({ ...user, isVeteran: true })),
    reduce((acc, user) => acc + user.salary, 0)
);

console.log(processUsers(users)); // 115000

📱 이벤트 처리와 DOM 조작

// 이벤트 핸들러 설정
const setupEventHandlers = () => {
    const buttons = document.querySelectorAll('.action-button');

    buttons.forEach(button => {
        button.addEventListener('click', (event) => {
            const action = event.target.dataset.action;

            switch(action) {
                case 'save':
                    handleSave(event);
                    break;
                case 'delete':
                    handleDelete(event);
                    break;
                case 'edit':
                    handleEdit(event);
                    break;
            }
        });
    });
};

// API 호출 관련 함수들
const api = {
    get: (url) => fetch(url).then(response => response.json()),
    post: (url, data) => fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
    }).then(response => response.json()),

    // 사용자 관련 API
    getUsers: () => api.get('/api/users'),
    createUser: (userData) => api.post('/api/users', userData),
    updateUser: (id, userData) => api.post(`/api/users/${id}`, userData)
};

// 비동기 작업 처리
const loadAndDisplayUsers = async () => {
    try {
        const users = await api.getUsers();
        const userList = users
            .filter(user => user.active)
            .map(user => `<li>${user.name} (${user.email})</li>`)
            .join('');

        document.getElementById('user-list').innerHTML = userList;
    } catch (error) {
        console.error('사용자 목록 로드 실패:', error);
    }
};

🔧 유틸리티 함수들

// 자주 사용하는 유틸리티 함수들
const utils = {
    // 배열 관련
    chunk: (array, size) => 
        Array.from({ length: Math.ceil(array.length / size) }, (_, i) =>
            array.slice(i * size, i * size + size)
        ),

    unique: array => [...new Set(array)],

    groupBy: (array, keyFn) =>
        array.reduce((groups, item) => {
            const key = keyFn(item);
            groups[key] = groups[key] || [];
            groups[key].push(item);
            return groups;
        }, {}),

    // 객체 관련
    pick: (obj, keys) =>
        keys.reduce((result, key) => {
            if (obj[key] !== undefined) {
                result[key] = obj[key];
            }
            return result;
        }, {}),

    omit: (obj, keys) =>
        Object.keys(obj)
            .filter(key => !keys.includes(key))
            .reduce((result, key) => {
                result[key] = obj[key];
                return result;
            }, {}),

    // 함수 관련
    debounce: (fn, delay) => {
        let timeoutId;
        return (...args) => {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => fn.apply(this, args), delay);
        };
    },

    throttle: (fn, interval) => {
        let lastTime = 0;
        return (...args) => {
            const now = Date.now();
            if (now - lastTime >= interval) {
                lastTime = now;
                fn.apply(this, args);
            }
        };
    }
};

// 사용 예제
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(utils.chunk(numbers, 3)); // [[1,2,3], [4,5,6], [7,8,9]]

const duplicates = [1, 2, 2, 3, 3, 3, 4];
console.log(utils.unique(duplicates)); // [1, 2, 3, 4]

const grouped = utils.groupBy(users, user => user.department);
console.log(grouped); // { "개발": [...], "디자인": [...], "마케팅": [...] }

마무리

화살표 함수는 ES6의 가장 유용한 기능 중 하나로, 코드를 더 간결하고 읽기 쉽게 만들어줍니다. 하지만 this 바인딩의 차이점을 정확히 이해하고 적절한 상황에서 사용하는 것이 중요합니다.

✅ 핵심 정리

  1. 문법: (매개변수) => 표현식 형태로 간결한 함수 작성
  2. this 바인딩: 화살표 함수는 자신만의 this를 갖지 않음
  3. 콜백 적합: 배열 메서드, Promise, 이벤트 핸들러에 이상적
  4. 메서드 부적합: 객체 메서드나 생성자 함수로는 사용 금지

화살표 함수를 올바르게 사용하면 더 모던하고 읽기 쉬운 JavaScript 코드를 작성할 수 있습니다!