
아무런 생각없이 쓰고 있던 콜백함수와 익명함수의 동작원리에 대해서 공부해보자
리액트나 넥스트나 바닐라 자바스크립트로 프로젝트를 하다보면 콜백함수와 익명함수를 셀 수 없이 많이 쓰는데 해당 함수들은 어떤 역할을 하며 어떤 용도로 쓰일까?
콜백함수는 어떤 함수의 인수로 함수가 전달된 함수이다.
즉, 인수로 전달된 함수를 콜백함수라고 한다.
인수로 전달된 함수는 전달받은 함수 내부에서 호출할 수 있다.
function multi(x, y, z, add) {
return add(x, y) * z;
}
const add = (x, y) => {
return x + y;
};
function result(x, y, z, add) {
console.log(multi(x, y, z, add));
}
result(1, 2, 3, add);
위와같이 "result"함수 호출 인수부분에 "add"함수를 인수(콜백함수)로 전달하고 해당 인수는 또 "multi"함수의 내부로 전달하는 방식이다.
그럼 "result" -> "multi" -> "add" 순서대로 함수가 실행되게 된다.
function multi(x, y, z, add) {
return add(x, y) * z;
}
function result(x, y, z, add) {
console.log(multi(x, y, z, add));
}
result(1, 2, 3, function (x, y) {
return x + y;
});
위와같이 익명함수도 함수의 인수로 전달할 수 있다.
보통 콜백함수는 일회성 함수인 경우가 많아서 익명함수를 많이 사용한다.
일반함수는 메모리를 상시 차지하고 있지만 익명함수는 한 번만 사용하는 함수로 만들어졌기 때문에 동작하는 시간 외에는 메모리를 차지하고 있지 않아서 자원관리에 효율적이다.
// 일반함수
function normalFunction (){
}
// 익명함수
function (){}
// 익명 화살표 함수
() => {}
콜백지옥에 빠지기 쉽다.
에러 처리가 어렵다.
addEventListner
const changeButton = document.querySelector(".changeTitleHandler");
const title = document.querySelector(".title");
changeButton.addEventListener("click", () => {
console.log("event active");
title.innerText = "change!!!";
});
이벤트감지 메서드인 addEventListner의 두번째 인수로 콜백함수가 들어간다.
고차함수
const numberArr = [1, 2, 3, 4, 5];
const changeNumberArr = numberArr.map((el) => {
return el * 2;
});
console.log(changeNumberArr);
고차함수란, 함수를 인자로 전달받거나 혹은 해당 함수를 통해서 결과를 반환해주는 메서드를 뜻한다.
고차함수의 종류에는 map, forEach, sort, find, reduce, filter 등등이 있다.
setTimeOut, setInterval
setTimeout(() => {
console.log("timeout : hello!");
}, 3000);
console.log("helloTimeout");
setInterval(() => {
console.log("setInterval : hello!!!!");
}, 1000);
console.log("helloInterval");

비동기 처리 함수인 setTimeout, setInterval에서도 콜백함수가 사용된다.
자바스크립트를 처음배우고 콜백함수의 챕터에 들어가게 된다면 콜백지옥이라는 단어를 들어봤을 것이다.
콜백지옥이란 무엇일까?
먼저 콜백함수란 함수 내부에 함수가 존재하여서 먼저 실행되는걸 콜백함수라고 한다.
콜백함수를 사용해야 한다는건 우리가 해결할 수 없는 것 혹은 적어도 정해지지 않은 시점에 코드를 실행해야 하는 메커니즘을 제공해야 한다는 것이다.
하지만 콜백의 중첩이 많아지면 콜백지옥에 빠지게 된다.
const fakeRequest = (url, success, fail) => {
const delay = Math.random();
setTimeout(() => {
if (delay > 0.5){
success("complete worked");
}else{
fail("fail worked");
}
},1000);
};
fakeRequest("book.com/", (reponse) => {
console.log(reponse);
fakeRequest("book.com/page2", (response) => {
console.log(response);
fakeRequest("book.com/page3", (response) => {
console.log(response);
}, (response) => {
console.log(response);
})
}, (response) => {
console.log(response);
})
}, (response)=> {
console.log(response);
})
위 코드는 가상의 서버에다가 데이터를 요청한 것이라 생각하자
여기서 주목해야 할 점은 데이터 요청이 연속적으로 이뤄진다는 점이다.
즉, 첫번째 요청에 성공적인 응답이 돌아온다면 두번째 요청이 들어가고 또 같은 방식으로 세번째 요청이 들어가는 것이다.
위와같이 콜백함수로 연속된 요청을 처리하기엔 코드의 가독성이 떨어지고 만약 응답에 실패했을 경우 어느 부분에서 실패했는지 알기가 힘들다.
이처럼 복잡한 콜백을 사용해야 할 경우 편하게 해주는게 Promise와 비동기 함수이다.
호이스팅에 의해서 메모리에 올라가는 일반함수와는 반대로 익명함수는 말 그대로 함수란 정의만 하고 해당 함수의 이름을 정의하지 않는 함수이다.
익명함수는 호이스팅이 일어나지 않고 실행할때만 메모리에 올라가는 함수이다.
그래서 보통은 일회성 함수에서 많이쓰이는 함수 형태이다.
console.log(add(1, 2));
function add(x, y) {
return x + y;
}
위와같이 일반함수로 하면 메모리에 올라가므로 대규모 프로젝트일 경우 제한적인 자원에 한계가 오기 쉽다.

로직이 동작하는 순서를 보면 "console.log"과 위에 있지만 밑에 있던 "add"일반함수가 먼저 호이스팅에 의해 메모리에 올라가는걸 확인할 수 있다.
function multi(x, y, z, add) {
return add(x, y) * z;
}
console.log(
multi(1, 2, 3, (x, y) => {
return x + y;
})
);
위와같이 익명함수는 호이스팅이 일어나지 않으므로 메모리에 바로 올라가지 않고 해당 호출부가 실행된다면 그 때 메모리에 올라가게 된다.
즉, 메모리에 올라가는 일반함수보다 메모리에 대한 부담이 적다.
그래서 일회성인 함수에는 익명함수를 쓰는게 자원관리차원에서 장점이 있다고 할 수 있다.