2025 / 01 / 31
오늘은 수업 시간에 즉시실행함수 / 재귀함수 / 콜백함수에 대해 배웠습니다.
재귀함수는 종종 처음으로 되돌리고 싶을 때 사용해봐서 익숙했지만
즉시실행함수와 콜백함수는 모르는 부분이 더 많았습니다.
특히 즉시실행함수는 아예 처음 보는 함수라 이런 함수가 있다는게 조금 신기했습니다.
즉시실행함수(IIFE)는 정의와 동시에 실행되는 함수입니다.
특정한 범위 내에서만 변수를 사용할 수 있게 하고 싶을 때 사용합니다.
이 함수는 한 번만 실행되고 종료됩니다. (선언되자마자 바로 실행)
- 함수를 괄호로 감싸고, 함수 호출도 괄호로 감싸는 방식으로 사용합니다.
(function() { // 코드 블록 console.log("IIFE 실행!"); })();
1) function( ) {...}
2) (function( ) {...})
3) ( )
1) 변수 스코프 제한
- IIFE 내부에서 정의된 변수는 함수 내부에서만 접근이 가능합니다.
- 외부와의 충독을 방지하고 전역 공간을 오염시키지 않도록 할 수 있습니다.
2) 전역 변수 오염 방지
- 여러 스크립트가 있을 때 전역 변수의 이름 충돌을 피하고 싶을 때 사용합니다.
3) 즉시 실행 로직 필요
- 일부 로직을 정의하고 바로 실행해야 할 때 유용합니다.
- 데이터를 초기화하거나 설정을 적용할 때 사용할 수 있습니다.
1) 즉시 실행하여 초기화하기
- 앱 초기화 코드처럼 사용할 수 있습니다.
- 전역 변수에 영향을 주지 않고, appName과 version은 IIFE 내부에서만 작동합니다.
(function() { let appName = "MyApp"; let version = "1.0"; console.log("App initialized:", appName, version); })();
2) 비동기 처리와 결합된 IIFE
- 비동기 코드와 결합되어 특정 로직을 즉시 실행할 때 유용합니다.
(function() { setTimeout(() => { console.log("비동기 작업 실행"); }, 1000); })();
장점
1) 스코프 보호
- 외부에서 접근할 수 없는 변수를 만들어서 코드의 안정성을 높입니다.
2) 전역 변수 충돌 방지
- 여러 코드가 있을 때, IIFE로 전역 변수를 방지할 수 있습니다.
3) 즉시 실행할 수 있는 로직
- 코드가 한 번 실행된 뒤 더 이상 필요하지 않은 로직을 처리할 수 있습니다.
단점
1) 읽기 어려울 수 있음
- 익명 함수와 괄호를 사용하여 작성되므로 처음 보는 사람에게는 직관적이지 않습니다.
2) 디버깅 어려움
- IIFE 내부의 코드에서 에러가 발생하면, 그 코드만 따로 디버깅하기 어려울 수 있습니다.
재귀함수는 자기 자신을 호출하는 함수입니다.
재귀 함수는 문제를 작은 부분 문제로 나누어 해결하는 데 사용됩니다.
1) 기저 조건(Base Case)
- 재귀 호출을 멈추는 조건입니다.
- 기저 조건이 없으면 계속 호출되어 무한 루프에 빠지게 됩니다.
2) 재귀 조건(Recursive Case)
- 자기 자신을 호출하는 부분입니다.
- 반복적으로 자기 자신을 호출하면서 문제를 점점 더 작은 문제로 쪼갭니다.
function printNumber(n){ // 매개변수 n의 값이 0보다 작다면 함수를 종료하겠습니다.(기저 조건) if(n <= 0){ return; // 함수를 끝낸다. } printNumber(n-1); // 자기 자신을 호출(재귀 조건) console.log(n); } printNumber(10);
1) 팩토리얼 계산
- 주어진 수 n에 대해 n x (n-1) x (n-2) x ... x 1의 값을 구하는 연산입니다.
// 재귀함수 응용 : 팩토리얼 계산 function factorial(n){ // 매개변수 n의 값이 0보다 작다면 함수를 종료하겠습니다. if(n === 0 || n === 1){ return 1; // 함수를 끝낸다. } return n * factorial(n-1); } console.log(factorial(5)); // 120
1) 기저 조건
2) 재귀 조건
3) 메모리 사용
장점
1) 간결하고 직관적
- 복잡한 문제를 단순화하여 쉽게 표현할 수 있습니다.
2) 문제를 나누어 해결
- 복잡한 문제를 점차적으로 해결해나가서 더 쉽게 처리가 가능합니다.
단점
1) 성능 문제
- 재귀 함수는 매번 새로운 함수 호출을 스택에 쌓기 때문에 메모리 사용이 많고 성능이 떨어질 수 있습니다.
2) 무한 재귀
- 기저 조건을 잘못 설정하거나, 재귀 종료가 없는 경우 무한 재귀에 빠져서 스택 오버플로우가 발생할 수 있습니다.
콜백함수는 다른 함수의 인수로 전달되어 실행되는 함수입니다.
어떤 함수에 인수로 전달되어 그 함수가 끝난 후 실행됩니다.
- 어떤 작업이 끝난 후에 실행되어야 할 함수를 다른 함수에 넘겨주는 방식입니다.
- 다른 함수가 그 작업을 마친 뒤, 전달된 콜백 함수를 실행하는 구조입니다.
// 콜백 함수 function greet(name, callback){ console.log(`hello, ${name}!`); callback(); } function sayGoodBye(){ console.log("Goodbye!"); } greet("Alice",sayGoodBye); // hello, Alice! // Goodbye!
- 여기서 greet 함수는 sayGoodbye라는 콜백 함수를 받습니다.
- greet 함수가 끝난 후, 콜백 함수인 sayGoodbye가 호출됩니다.
1) 배열의 forEach
- num.forEach( )는 배열 num의 각 요소에 대해 주어진 콜백 함수를 실행합니다.
- forEach 메서드는 콜백 함수를 배열의 각 요소에 대해 호출하며, 콜백 함수에는 두 개의 인수가 전달됩니다.
const num = [1,2,3,4,5]; num.forEach((item, index)=>{ console.log(`배열의 요소는 ${item}이고 인덱스는 [${index}]입니다.`); }) /* 출력 결과 배열의 요소는 1이고 인덱스는 [0]입니다. 배열의 요소는 2이고 인덱스는 [1]입니다. 배열의 요소는 3이고 인덱스는 [2]입니다. 배열의 요소는 4이고 인덱스는 [3]입니다. 배열의 요소는 5이고 인덱스는 [4]입니다. */
2) 주어진 횟수만큼 반복작업 수행
- repeat 함수는 두 개의 매개변수를 받습니다.
- number 만큼 반복하면서 그에 맞게 콜백 함수가 호출됩니다.
- for 반복문으로 i가 0부터 number - 1까지 반복하면서 매번 callback(i)를 실행합니다.
- callback(i)는 현재 반복 중인 인덱스 값(i)을 콜백 함수로 전달합니다.
function repeat(number, callback) { for (let i = 0; i < number; i++) { callback(i); } } function callback(i) { console.log(`인덱스 : ${i}`); } repeat(5, callback); /* 출력 결과 인덱스 : 0 인덱스 : 1 인덱스 : 2 인덱스 : 3 인덱스 : 4 */
3) 숫자 배열 필터링(홀수 / 짝수 구분)
① 배열 순회 후 출력
- filterArray: 배열을 순회하며 짝수는 newArray에 추가하고, 짝수와 홀수를 각각 출력합니다.
- isEven: 짝수를 출력합니다.
- odd: 홀수를 출력합니다.
// filterArray 함수는 배열을 순회하면서 짝수만 새로운 배열에 추가하고 반환 function filterArray(array, callback) { let newArray = []; // 짝수를 저장할 배열 for (let i of array) { if (i % 2 == 0) { newArray.push(i); // 짝수일 경우 newArray에 추가 isEven(i); // 짝수 출력 } else { odd(i); // 홀수 출력 } } return newArray; // 짝수만 담긴 배열 반환 } // 짝수를 출력하는 함수 function isEven(i) { console.log("짝수 : " + i); } // 홀수를 출력하는 함수 function odd(i) { console.log("홀수 : " + i); } // filterArray 함수 호출 후 결과 확인 const result = filterArray([1, 2, 3, 4], isEven); console.log("짝수 배열:", result); // 반환된 짝수 배열 출력 /* 출력 결과 홀수 : 1 짝수 : 2 홀수 : 3 짝수 : 4 짝수 배열: [ 2, 4 ] */
② 배열 순회 후 객체 분할 대입 사용
- 리턴 값을 2개를 받은 후 객체 분할 대입을 사용하였습니다.
- 분할 대입에 관련한 내용은 추후 포스팅하겠습니다!
function filterArray1(numArray, IsEven) { let evenNumbers = []; let oddNumbers = []; for (let i = 0; i < numArray.length; i++) { if (IsEven(numArray[i])) { evenNumbers.push(numArray[i]); } else { oddNumbers.push(numArray[i]); } } return { even: evenNumbers, odd: oddNumbers }; } function IsEven(number) { return number % 2 == 0; } console.log(filterArray1([1, 2, 3, 4], IsEven)); /* 출력 결과 { even: [ 2, 4 ], odd: [ 1, 3 ] } */
장점
1) 비동기 작업 처리
- 콜백 함수는 비동기 작업에서 매우 유용합니다.
- 시간이 걸리는 작업에서 작업이 끝난 후에 실행할 코드를 지정할 수 있습니다.
2) 코드의 재사용성
- 다양한 함수에 인수로 전달할 수 있기 때문에 같은 로직을 재사용할 수 있습니다.
- 코드의 중복을 줄이고 유연성을 높일 수 있습니다.
3) 함수형 프로그래밍에서의 활용
- 함수형 프로그래밍에서 중요한 개념으로, 함수 자체를 다른 함수에 인수로 전달하여 조합하거나 추상화하는 데 활용됩니다.
- 코드의 가독성 및 유지보수성을 높일 수 있습니다.
단점
1) 콜백 지옥 (Callback Hell)
- 콜백 함수가 여러개 중첩되면 코드가 매우 복잡해지고 읽기 어려워지는 현상이 발생합니다.
- 여러 비동기 작업을 순차적으로 처리하려면 콜백이 계속 중첩되며, 이는 코드의 가독성과 유지보수성을 크게 떨어뜨립니다.
2) 예외 처리 어려움
- 콜백 함수 내에서 발생한 에러를 처리하는 것이 어려운 경우가 많습니다.
- 에러를 처리하려면 콜백 함수마다 일일이 에러 처리 코드를 작성해야 하기 때문에 중복되고 관리하기 어려운 코드가 될 수 있습니다.
3) 상태 관리 어려움
- 콜백 함수가 많아질수록 코드 내에서 상태를 추적하고 관리하는 것이 어려워질 수 있습니다.
4) 디버깅의 어려움
- 비동기 코드에서 콜백 함수는 주로 나중에 실행되므로, 코드 실행 흐름을 추적하는 데 어려움을 겪을 수 있습니다.
- 콜백이 여러 개 중첩되어 있을 경우, 문제가 발생한 부분을 정확히 찾아내는 것이 복잡하고 시간이 오래 걸릴 수 있습니다.
18일차(1) 후기
- 콜백함수의 응용 문제를 풀 때, 콜백함수라는 것을 의식하고 써본적이 없어서 그런지.. 어떻게 작동되고 넘어가는지 이해하는게 조금 어려웠습니다.
- 아직은 함수를 사용하는게 미숙한 부분이 있고, 함수 활용이 살짝 어려운 것 같습니다.
- 함수를 사용하는데 헷갈리는 것이 없도록 차근차근 공부하겠습니다! ・。゚(゚^ω^゚)。゚・