1. 함수 표현식: 함수를 값으로 다루는 유연함
함수 표현식은 함수를 변수에 할당하는 방식을 말합니다. 이는 코드의 가독성과 재사용성을 높이며, 특히 고차 함수와 함께 사용할 때 그 진가를 발휘합니다.
1.1. 함수 표현식의 기본: 정의와 형태
- 정의: 함수 표현식은
function
키워드 뒤에 이름이 없거나(익명 함수), 변수 이름이 앞에 오는 형태로 작성됩니다. - 형태:
const myFunction = function() {
console.log("Hello, World!");
};
myFunction(); // "Hello, World!" 출력
1.2. 익명 함수와 이름 있는 함수: 상황에 따른 선택
- 익명 함수 (Anonymous Function): 이름이 없는 함수로, 다른 변수에 할당하거나 다른 함수의 인자로 전달할 때 주로 사용됩니다.
const greet = function(name) {
console.log(`Hello, ${name}!`);
};
greet("John"); // "Hello, John!" 출력
// 예제: 배열의 forEach 메서드에 익명 함수 사용
const numbers = [1, 2, 3];
numbers.forEach(function(number) {
console.log(number * 2); // 2, 4, 6 출력
});
- 이름 있는 함수 (Named Function): 함수 표현식에 이름을 붙일 수도 있습니다. 이는 재귀 호출이나 디버깅 시 유용하게 활용됩니다.
const factorial = function calculateFactorial(n) {
if (n === 0) {
return 1;
} else {
return n * calculateFactorial(n - 1); // 재귀 호출
}
};
console.log(factorial(5)); // 120 출력
// 예제: 피보나치 수열 계산 함수
const fibonacci = function calculateFibonacci(n) {
if (n <= 1) {
return n;
}
return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
};
console.log(fibonacci(10)); // 55 출력
1.3. 즉시 실행 함수 표현식 (IIFE): 스코프 보호와 모듈화
IIFE (Immediately Invoked Function Expression)는 정의되자마자 즉시 실행되는 함수를 말합니다. 전역 스코프 오염을 방지하고, 모듈화를 위해 자주 사용되는 중요한 패턴입니다.
(function() {
console.log("This runs immediately!");
})(); // "This runs immediately!" 출력
// 예제: IIFE를 사용한 변수 스코프 제한
(function() {
const message = "Hello from IIFE!";
console.log(message); // "Hello from IIFE!" 출력
})();
// console.log(message); // ReferenceError: message is not defined (message는 IIFE 내부에서만 유효)
1.4. 함수 표현식을 활용한 실용 예제: 계산기 객체 만들기
함수 표현식을 활용하여 간단하면서도 확장성 있는 계산기 객체를 만들어 보겠습니다.
// 덧셈 함수
const add = function(a, b) {
return a + b;
};
// 뺄셈 함수
const subtract = function(a, b) {
return a - b;
};
// 곱셈 함수
const multiply = function(a, b) {
return a * b;
};
// 나눗셈 함수
const divide = function(a, b) {
if (b === 0) {
return "Division by zero is not allowed.";
}
return a / b;
};
// 나머지 연산 함수
const modulo = function(a, b) {
if (b === 0) {
return "Modulo by zero is not allowed";
}
return a % b;
}
// 계산기 객체
const calculator = {
add, // 더하기
subtract, // 빼기
multiply, // 곱하기
divide, // 나누기
modulo // 나머지
};
// 사용 예시
console.log(calculator.add(5, 3)); // 8 출력
console.log(calculator.subtract(10, 4)); // 6 출력
console.log(calculator.multiply(2, 6)); // 12 출력
console.log(calculator.divide(8, 2)); // 4 출력
console.log(calculator.divide(8, 0)); // "Division by zero is not allowed." 출력
console.log(calculator.modulo(10, 3)); // 1 출력
위 예제에서는 add
, subtract
, multiply
, divide
, modulo
다섯 가지 연산을 수행하는 계산기 객체를 함수 표현식을 사용하여 구현했습니다. 각 연산은 독립적인 함수로 구현되어 있어 필요에 따라 쉽게 기능을 확장할 수 있습니다.
2. 클로저: 데이터를 품은 함수로 강력한 기능 구현하기
클로저는 함수와 그 함수가 선언된 렉시컬 환경(lexical environment)의 조합입니다. 내부 함수가 외부 함수의 변수에 접근할 수 있도록 해주며, 이를 통해 데이터 은닉, 상태 유지 등 객체 지향 프로그래밍의 핵심 기능들을 구현할 수 있습니다.
2.1. 클로저의 정의: 함수, 스코프, 그리고 렉시컬 환경
- 함수 (Function): 자바스크립트에서 함수는 자신의 스코프를 가집니다.
- 스코프 (Scope): 변수의 유효 범위를 말하며, 내부 함수는 외부 함수의 변수에 접근할 수 있습니다.
- 클로저 (Closure): 외부 함수의 실행이 종료된 후에도 내부 함수를 통해 외부 변수에 접근할 수 있는 기능입니다.
2.2. 클로저의 동작 원리: 스코프 체이닝 이해하기
- 외부 함수가 호출되면 내부 함수가 생성됩니다.
- 내부 함수는 외부 함수의 변수를 기억합니다. (스코프 체이닝)
- 외부 함수의 실행이 종료되어도, 내부 함수를 통해 외부 변수에 접근할 수 있습니다.
2.3. 클로저 예제: 카운터 함수 만들기
function createCounter() {
let count = 0; // 외부 함수 변수
return function() { // 내부 함수 (클로저)
count += 1;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1 출력
console.log(counter()); // 2 출력
console.log(counter()); // 3 출력
// 예제: 스텝(step)을 설정할 수 있는 카운터
function createStepCounter(step) {
let count = 0;
return function() {
count += step;
return count;
};
}
const stepCounter = createStepCounter(2);
console.log(stepCounter()); // 2 출력
console.log(stepCounter()); // 4 출력
createCounter
함수는 count
변수를 갖는 외부 함수이고, count
를 증가시키고 반환하는 익명 함수(내부 함수)를 반환합니다. counter
변수에 createCounter
의 반환 값, 즉 내부 함수를 할당합니다. counter
함수를 호출할 때마다 count
값이 증가하고, 그 값이 반환됩니다. createCounter
함수의 실행이 종료된 후에도 counter
함수는 count
변수에 계속 접근할 수 있습니다. 이것이 바로 클로저의 핵심입니다. createStepCounter
함수는 인자로 step
을 받아서 카운트 증가폭을 조절할 수 있도록 확장한 버전입니다.
2.4. 클로저 활용: 데이터 은닉과 상태 유지
- 데이터 은닉 (Data Hiding): 클로저를 통해 특정 데이터를 외부로부터 보호하고, 정해진 메서드를 통해서만 접근하도록 제어할 수 있습니다.
function secretHolder(secret) {
return {
getSecret: function() {
return secret;
},
setSecret: function(newSecret) {
secret = newSecret;
}
};
}
const mySecret = secretHolder("My secret password");
console.log(mySecret.getSecret()); // "My secret password" 출력
mySecret.setSecret("New secret password");
console.log(mySecret.getSecret()); // "New secret password" 출력
// 예제: 비밀번호 확인 기능 추가
function enhancedSecretHolder(secret, password) {
let attempts = 0;
return {
getSecret: function(enteredPassword) {
attempts++;
if (enteredPassword === password) {
console.log(`Accessed after ${attempts} attempts.`);
return secret;
} else {
console.log("Incorrect password!");
return null;
}
},
setSecret: function(newSecret, enteredPassword) {
if (enteredPassword === password) {
secret = newSecret;
console.log("Secret updated successfully.");
} else {
console.log("Incorrect password! Cannot update secret.");
}
}
};
}
const secureSecret = enhancedSecretHolder("Highly confidential", "qwerty");
console.log(secureSecret.getSecret("wrongpassword")); // "Incorrect password!" 출력, null 반환
console.log(secureSecret.getSecret("qwerty")); // "Accessed after 2 attempts." 출력, "Highly confidential" 반환
secureSecret.setSecret("New top secret", "qwerty"); // "Secret updated successfully." 출력
console.log(secureSecret.getSecret("qwerty")); // "Accessed after 3 attempts." 출력, "New top secret" 반환
secretHolder
함수는 secret
변수를 캡슐화하고, getSecret
과 setSecret
메서드를 통해서만 secret
에 접근하도록 제한합니다. enhancedSecretHolder
함수는 비밀번호를 입력해야만 기밀 정보(secret
)에 접근하거나 수정할 수 있도록 보안을 강화했습니다.
- 상태 유지 (State Preservation): 클로저를 사용하여 함수 호출 간에 상태를 유지하고 관리할 수 있습니다.
function makeBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function(amount) {
balance += amount;
console.log(`Deposited ${amount}, new balance is ${balance}`);
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
console.log(`Withdrew ${amount}, new balance is ${balance}`);
} else {
console.log("Insufficient funds!");
}
},
getBalance: function() {
return balance;
}
};
}
const account = makeBankAccount(100);
account.deposit(50); // "Deposited 50, new balance is 150" 출력
account.withdraw(30); // "Withdrew 30, new balance is 120" 출력
console.log(account.getBalance()); // 120 출력
// 예제: 거래 내역 기록 기능 추가
function makeAdvancedBankAccount(initialBalance) {
let balance = initialBalance;
let transactionHistory = [];
return {
deposit: function(amount) {
balance += amount;
transactionHistory.push(`Deposited ${amount}`);
console.log(`Deposited ${amount}, new balance is ${balance}`);
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
transactionHistory.push(`Withdrew ${amount}`);
console.log(`Withdrew ${amount}, new balance is ${balance}`);
} else {
console.log("Insufficient funds!");
}
},
getBalance: function() {
return balance;
},
getHistory: function() {
return transactionHistory;
}
};
}
const advancedAccount = makeAdvancedBankAccount(500);
advancedAccount.deposit(100); // "Deposited 100, new balance is 600" 출력
advancedAccount.withdraw(50); // "Withdrew 50, new balance is 550" 출력
console.log(advancedAccount.getHistory()); // ["Deposited 100", "Withdrew 50"] 출력
makeBankAccount
함수는 balance
변수를 클로저 내부에 유지하여 계좌 잔액 상태를 관리합니다. makeAdvancedBankAccount
함수는 거래 내역을 transactionHistory
배열에 저장하여 계좌 거래 내역을 추적할 수 있도록 기능을 확장했습니다.
3. 콜백 함수: 비동기 처리와 이벤트 핸들링의 핵심
콜백 함수는 다른 함수의 인자로 전달되어 특정 시점에 실행되는 함수입니다. 비동기 처리, 이벤트 핸들링 등에서 중추적인 역할을 하며, 자바스크립트의 유연성과 반응성을 극대화하는 핵심 요소입니다.
3.1. 콜백 함수의 개념: 정의와 용도
- 정의: 콜백 함수는 특정 작업이 완료된 후 호출되는 함수를 말합니다.
- 용도: 주로 비동기 작업, 이벤트 리스너, 배열 메서드 등에서 활용됩니다.
3.2. 콜백 함수 예제: 비동기 처리, 이벤트 처리, 배열 메서드 활용
3.2.1. 비동기 처리: setTimeout
을 활용한 데이터 가져오기
setTimeout
함수는 일정 시간 지연 후 콜백 함수를 실행합니다. 이를 활용하여 비동기 데이터 로딩을 모방해 보겠습니다.
function fetchData(callback) {
setTimeout(function() {
const data = { message: "Data fetched successfully!" };
callback(data); // 데이터 로딩 완료 후 콜백 함수 실행
}, 2000); // 2초 후 실행
}
function handleData(data) {
console.log(data.message);
}
fetchData(handleData); // 2초 후 "Data fetched successfully!" 출력
// 예제: 에러 처리 기능 추가
function fetchDataWithErrorHandling(callback, errorCallback) {
setTimeout(function() {
const success = Math.random() < 0.8; // 80% 확률로 성공
if (success) {
const data = { message: "Data fetched successfully!" };
callback(data);
} else {
const error = { message: "Error fetching data!" };
errorCallback(error);
}
}, 2000);
}
function handleError(error) {
console.error(error.message);
}
fetchDataWithErrorHandling(handleData, handleError);
fetchData
함수는 2초 후에 callback
함수를 호출하여 데이터를 전달합니다. handleData
함수는 fetchData
에 전달된 콜백 함수로, 데이터를 받아 처리합니다. fetchDataWithErrorHandling
함수는 성공/실패 여부에 따라 callback
또는 errorCallback
을 호출하여 에러 처리를 추가했습니다.
3.2.2. 이벤트 처리: 버튼 클릭 이벤트 핸들링
웹 페이지에서 발생하는 사용자 이벤트(예: 버튼 클릭)를 처리하기 위해 콜백 함수를 사용합니다.
<button id="myButton">Click me!</button>
<script>
document.getElementById("myButton").addEventListener("click", function() {
alert("Button clicked!");
});
// 예제: 더블 클릭 이벤트 처리
document.getElementById("myButton").addEventListener("dblclick", function() {
alert("Button double-clicked!");
});
</script>
addEventListener
메서드를 사용하여 버튼에 click
이벤트 리스너를 등록하고, 콜백 함수를 연결합니다. 사용자가 버튼을 클릭하면 콜백 함수가 실행되어 "Button clicked!" 알림이 뜹니다. dblclick
이벤트 리스너를 추가하여 더블 클릭 이벤트도 처리하도록 확장했습니다.
3.2.3. 배열 메서드: map
, filter
, reduce
를 활용한 데이터 가공
자바스크립트 배열은 map
, filter
, reduce
와 같은 유용한 메서드를 제공하며, 이들은 모두 콜백 함수를 인자로 받아 배열 데이터를 효과적으로 가공합니다.
const numbers = [1, 2, 3, 4];
// map: 각 요소를 2배로 만들기
const doubled = numbers.map(function(num) {
return num * 2;
});
console.log(doubled); // [2, 4, 6, 8] 출력
// filter: 짝수만 추출하기
const evens = numbers.filter(function(num) {
return num % 2 === 0;
});
console.log(evens); // [2, 4] 출력
// reduce: 배열 요소의 합계 계산하기
const sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0); // 초기값 0
console.log(sum); // 10 출력
map
메서드는 배열의 각 요소에 대해 콜백 함수를 실행하고, 그 결과들을 모아 새로운 배열을 만듭니다.filter
메서드는 콜백 함수가true
를 반환하는 요소들만 모아 새로운 배열을 만듭니다.reduce
메서드는 배열의 각 요소에 대해 콜백 함수를 실행하여 하나의 결과값을 만듭니다.
3.3. 콜백 지옥 (Callback Hell)과 해결 방안: Promise와 async/await
콜백 함수를 중첩해서 사용하면 코드가 복잡해지고 가독성이 떨어지는 "콜백 지옥" 현상이 발생할 수 있습니다.
doSomething(function(result1) {
doSomethingElse(result1, function(result2) {
doThirdThing(result2, function(result3) {
console.log('All done:', result3);
});
});
});
이러한 문제는 Promise나 async/await와 같은 최신 자바스크립트 기능을 사용하여 해결할 수 있습니다. 이들은 비동기 코드를 보다 간결하고 가독성 좋게 작성하도록 돕는 강력한 도구입니다.
결론: 함수 표현식, 클로저, 콜백 함수 정복으로 자바스크립트 마스터로!
본 포스트에서는 자바스크립트 함수의 핵심 개념인 함수 표현식, 클로저, 콜백 함수에 대해 깊이 있게 탐구했습니다. 이들은 자바스크립트의 강력한 기능을 뒷받침하는 근간이며, 효율적이고 유연한 코드를 작성하는 데 필수적입니다.
- 함수 표현식은 함수를 값으로 다루어 코드의 가독성과 재사용성을 높입니다.
- 클로저는 데이터 은닉과 상태 유지를 가능하게 하여, 객체 지향 프로그래밍과 모듈화를 지원합니다.
- 콜백 함수는 비동기 처리와 이벤트 핸들링을 위한 핵심 도구입니다.
이러한 개념들을 깊이 이해하고 활용하면, 더욱 강력하고 효율적인 자바스크립트 코드를 작성할 수 있습니다. 특히, 클로저와 콜백 함수는 처음에는 다소 헷갈릴 수 있지만, 다양한 예제를 통해 반복적으로 연습하면 금방 익숙해질 수 있습니다. 본 포스트가 여러분의 자바스크립트 함수 마스터를 향한 여정에 든든한 길잡이가 되었기를 바랍니다!
'프로그래밍 > Javascript' 카테고리의 다른 글
DOM (Document Object Model): 웹 페이지의 구조와 상호작용을 위한 모든 것 (0) | 2025.02.11 |
---|---|
자바스크립트 배열 정복: 개념부터 다차원 배열, 유용한 메소드까지 총정리! (0) | 2025.02.10 |
자바스크립트 객체의 모든 것: 기초부터 활용까지 (0) | 2025.02.10 |
자바스크립트 기본 정복: 변수부터 함수까지, 핵심 가이드 (0) | 2025.02.10 |
자바스크립트: 웹 세상을 움직이는 마법의 언어 (0) | 2025.02.10 |