반응형
화살표 함수란?
화살표 함수(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 바인딩의 차이점을 정확히 이해하고 적절한 상황에서 사용하는 것이 중요합니다.
✅ 핵심 정리
- 문법:
(매개변수) => 표현식형태로 간결한 함수 작성 - this 바인딩: 화살표 함수는 자신만의 this를 갖지 않음
- 콜백 적합: 배열 메서드, Promise, 이벤트 핸들러에 이상적
- 메서드 부적합: 객체 메서드나 생성자 함수로는 사용 금지
화살표 함수를 올바르게 사용하면 더 모던하고 읽기 쉬운 JavaScript 코드를 작성할 수 있습니다!
'JavaScript > 함수 완전 정복' 카테고리의 다른 글
| JavaScript 콜백 함수 개념 이해 - 간단 가이드 (0) | 2025.06.24 |
|---|---|
| JavaScript 함수의 매개변수와 반환값 완전 가이드 (3) | 2025.06.06 |
| JavaScript 함수 선언식 vs 표현식 완벽 가이드 (0) | 2025.06.06 |