자바스크립트에서 콜백 함수는 함수의 제어권을 다른 함수에게 넘겨주는 방식을 의미합니다.
즉, 함수를 직접 호출하는 대신 다른 함수의 인자로 전달하여, 그 함수가 언제, 어떤 인자로, 몇 번 호출할지를 결정하도록 위임하는 것입니다.예를 들어
forEach,setTimeout,addEventListener같은 내장 함수들은 모두 콜백 함수를 인자로 받아 내부에서 실행 시점을 제어합니다.
이때 호출의 흐름, 즉 제어권은 호출자가 아닌forEach,setTimeout과 같이 해당 함수에게 넘어가며, 그 함수가 조건에 맞을 때 콜백을 실행합니다.즉, "제어권이 이양된다"는 말은 함수 호출의 주체와 실행 시점을 내가 아닌, 콜백을 전달받은 함수가 결정한다는 의미입니다.
callback()을 호출하는 것이 아니라, 다른 함수가 대신 호출하는 함수function greeting(name) {
console.log(`Hello, ${name}`);
}
function processUserInput(callback) {
const name = "현진";
callback(name); // 여기서 greeting이 호출됨
}
processUserInput(greeting); // 콜백 전달
위 예제에서
processUserInput은 콜백(greeting)을 전달받아 실행 시점을 스스로 결정한다.
➡️ 이때 제어권(Control)이processUserInput함수로 넘어간다.
"제어권이 이양된다"는 말은 다음을 의미한다.
함수 호출의 시점, 횟수, 전달되는 인자 등을 호출자가 아닌 콜백을 받은 함수가 결정한다는 것
즉,
이 개념은 자바스크립트의 비동기 처리나 고차 함수(Higher-Order Function)에서 핵심적인 동작 원리이다.
🔎 고차 함수란?
- 함수를 인자로 받거나, 함수를 반환(혹은 둘 다)하는 함수
- 전제: 자바스크립트에서 함수는 일급 객체이므로 값처럼 전달·반환할 수 있다.
🔎 고차 함수와 제어권 이양과의 연결
- 콜백을 인자로 받는 순간, 콜백의 실행 시점/횟수/인자를 받은 함수가 통제한다. → 제어권 이양
- 이로써 동작(언제/어떻게 실행)은 감추고, 행위(무엇을 할지)만 바깥에서 주입 → 추상화·재사용성·테스트 용이성 상승
🔎 고차 함수 대표 예시 (JS 표준 API 중심)
- 배열 고차 함수
➡️ 모두 함수를 인자로 받는 고차 함수로, 콜백 호출 방식은 메서드가 결정함[1, 2, 3].map(x => x * 2); // 콜백의 반환값으로 새 배열 생성 [1, 2, 3].filter(x => x % 2); // 콜백의 진리값으로 요소 선별 [1, 2, 3].reduce((acc, x) => acc + x, 0); // 누적 계산 [1, 2, 3].forEach(x => console.log(x)); // 부수효과
- 비동기/이벤트 영역
➡️ 콜백 인자를 받아 비동기 시점에 호출함 → 고차 함수 패턴setTimeout(() => { /* ... */ }, 0); // 타이머 시스템이 시점 결정 btn.addEventListener('click', (e) => { /* ... */ }); // 브라우저 이벤트 시스템이 호출 Promise.resolve(1).then(v => v + 1); // 엔진이 마이크로태스크로 스케줄
- 함수를 반환하는 고차 함수
➡️const withLog = fn => (...args) => { console.time('t'); try { return fn(...args); } finally { console.timeEnd('t'); } }; const sum = (a, b) => a + b; const loggedSum = withLog(sum); loggedSum(1, 2);withLog는 함수를 받아 새 함수를 반환함
function run(callback) {
callback(); // 내가 직접 호출함
}
run(() => console.log("내가 직접 제어하는 호출"));➡️ 콜백을 즉시 실행하므로, 제어권이 이양되지 않음setTimeout(() => console.log("제어권이 타이머에 있음"), 2000);➡️ setTimeout이 콜백의 실행 시점(2초 후)을 결정하므로, 제어권이 setTimeout에게 이양됨forEach, map, filter⭐ 제어권 주체: 각 메서드의 내부 구현
⭐ 핵심: "언제/몇 번/무슨 인자로 콜백을 부를지"를 메서드가 결정
공통 규칙
| 항목 | 내용 |
|---|---|
| 호출 횟수 | 배열의 "실재하는 인덱스"마다 한 번 희소 배열의 구멍(hole)은 건너뜀 |
| 호출 시점 | 메서드 실행 중 동기적으로 즉시 (비동기 아님) |
| 전달 인자 | (요소값, 인덱스, 원본배열)의 3개를 메서드가 고정 규약으로 넣어줌 |
| this 바인딩 | 두 번째 인자 thisArg로 콜백의 this를 지정할 수 있음 |
| 중간 변경 영향 | 순회 중 배열 길이/값을 바꾸면 명세에 따라 그 이후 호출에 영향 (예: 뒤에 추가된 요소는 일반적으로 순회 대상 아님) |
| 중단 불가 | forEach는 break/return으로 중단 불가필요 시 some/every 사용을 고려 |
💡 희소 배열(Sparse Array)란?
- 배열의 인덱스가 연속적이지 않은 배열
- 배열의 길이는 크지만 중간 인덱스에 실제 값이 존재하지 않는 상태
- 이렇게 값이 비어 있는 인덱스를 구멍(hole)이라고 부른다.
const arr = [10, , 30]; console.log(arr.length); // 3이 배열의 인덱스는 다음과 같다.
인덱스 값 상태 0 10 존재 1 ❌ (없음) 구멍(hole) 2 30 존재 ➡️ 즉, 길이는 3이지만 실제로는 값이 2개만 있는 배열이다.
💡 구멍(hole)이란?
배열 내부에서 값이 전혀 할당되지 않은 인덱스 공간을 의미한다.
undefined랑은 다르다.const a = [undefined]; // 요소 1개 존재, 값이 undefined const b = [,]; // 요소 자체가 없음 (hole) console.log(0 in a); // true -> 인덱스 0 존재 console.log(0 in b); // false -> 인덱스 0 비어있음➡️ 즉,
undefined는 존재하지만 값이 없는 것이고, hole은 존재조차 하지 않는 것이다.💡 "희소 배열의 구멍을 건너뛴다"는 말의 의미
배열 메서드(
forEach,map,filter,some,every,reduce) 중 일부는 실제로 존재하는 인덱스에만 콜백을 실행한다.즉, 구멍(hole)이 있으면 그 인덱스는 아예 무시하고 지나
forEachundefined)mapfiltersetTimeout, setInterval⭐ 제어권 주체: 브라우저(Web API) / Node 타이머 서브시스템
⭐ 핵심: 시간 경과를 관찰하고, 만기 시 태스크 큐에 콜백을 예약하는 쪽이 타이머 시스템
setTimeout(fn, delay)delay 밀리초 이후 최소 지연 뒤에 실행 기회 제공setTimeout(fn, d, a, b...)처럼 추가 인자를 콜백에 전달 가능(브라우저/Node 지원)setInterval(fn, interval)addEventListener⭐ 제어권 주체: 브라우저의 이벤트 시스템 (이벤트 루프 + DOM 이벤트 흐름)
⭐ 핵심: 실제 사용자/DOM 이벤트가 발생할 때 브라우저가 콜백 호출 여부/순서/인자(Event 객체)를 결정
규칙
| 항목 | 내용 |
|---|---|
| 호출 조건/시점 | 해당 타입 이벤트 발생 시(클릭, 입력, 네트워크, 커스텁) 비동기적으로 큐에 전달되어 콜백 실행 |
| 전달 인자 | 첫 번째 인자로 이벤트 객체(MouseEvent, KeyboardEvent 등)를 브라우저가 생성/전달 |
| 캡처링/버블링 | useCapture 또는 options.capture에 따라 이벤트 흐름 단계에서 호출되는지 결정 |
| 리스너 다중 등록/순서 | 같은 노드·같은 단계에서는 등록 순서대로 호출(명세 규약) |
💡 브라우저의 이벤트 시스템이란?
- 사용자의 행동(click, input, scroll 등)이나 브라우저 내부의 상태 변화(load, error, network 등)를 감지하고,
이에 따라 등록된 콜백(이벤트 핸들러)을 실행해주는 메커니즘 전체- 브라우저 안에서 이벤트가 감지되고 → 전달되고 → 콜백 실행까지 이뤄지는 전체 자동 흐름
💡 브라우저의 이벤트 시스템 구성 요소
- 이벤트 루프(Event Loop): "언제 콜백을 실행할지"를 결정하는 브라우저의 비동기 처리 엔진
- DOM 이벤트 흐름(Event Flow): "어떤 순서로 어떤 노드에게 이벤트를 전달할지"를 결정하는 DOM 구조 기반의 전달 규칙
➡️ 즉, 이벤트 루프는 시간의 제어("언제 실행할까?"), DOM 이벤트 흐름은 공간의 제어("누가 받을까?")를 담당함
💡 전체 이벤트 처리 흐름 예시
button.addEventListener("click", () => { console.log("Clicked!"); });
- 사용자가 마우스를 클릭 → OS에서 브라우저로 "click" 신호 전달
- 브라우저의 이벤트 시스템이 click 이벤트 발생을 감지
- 해당 이벤트가 브라우저의 이벤트 큐(event queue)에 등록
- 이벤트 루프(Event Loop)가 현재 실행 중인 콜스택이 비면, 큐에 있는 이벤트를 꺼내 실행
- DOM 트리 상에서 캡처링 → 타깃 → 버블링 순서로 이벤트 전달
- addEventListener로 등록된 콜백이 해당 단계(캡처링 or 버블링)에 맞게 호출됨
💡 DOM 이벤트 흐름
HTML 문서는 트리 구조로 되어 있는데, 이벤트가 발생하면 그 트리를 따라 이동한다.
- 캡처링 단계 (Capturing Phase)
- 최상위(
window→document→html→body→ ...)에서 이벤트 대상 요소까지 내려가며 이벤트를 전달함addEventListener("click", handler, { capture: true })로 감지 가능- 타깃 단계 (Target Phase)
- 실제 이벤트가 발생한 요소(예:
<button>)에 도달- 버블링 단계 (Bubbling Phase)
- 다시 위쪽으로 거슬러 올라가며 부모 요소들에게 이벤트를 전달함
- 기본값(
{ capture: false })일 때는 이 단계에서 실행됨💡 제어권 이양과의 연결
button.addEventListener("click", callback);
- 여기서
callback은 코드 작성자가 직접 호출하지 않음- 클릭 시점, 호출 순서, 인자(Event 객체)는 브라우저의 이벤트 시스템이 전적으로 관리함
➡️ 즉, 언제, 어떤 인자(event 객체)로 콜백을 실행할지 결정하는 권한이 브라우저의 이벤트 시스템에게 이양된 것임
fetch, Promise.then, async/await⭐ 제어권 주체: Web API + JS 엔진의 Promise/마이크로태스크 스케줄러
⭐ 핵심: 비동기 작업의 완료/실패 시점과 그에 따른 콜백(then/catch)의 마이크로태스크 실행을 런타임이 결정
fetch(url, options).then(res => ...) 콜백은 마이크로태스크 큐에 스케줄됨(콜스택이 비면 즉시 처리)Promise.then(onFulfilled, onRejected)async/awaitawait는 우변의 Promise가 settle되면 그 이후 코드를 마이크로태스크로 이어서 실행하라는 스케줄링 지시await 중 거부되면 예외로 던져짐 → try/catch로 처리(제어 흐름을 런타임이 스케줄)| 구분 | 설명 |
|---|---|
| 언제 호출할지 | 호출 시점을 전달받은 함수가 결정 |
| 몇 번 호출할지 | 반복, 조건, 이벤트 횟수 등은 내부 로직에 따라 다름 |
| 무엇을 인자로 줄지 | 전달받은 함수가 콜백에 어떤 데이터를 전달할지도 스스로 결정 |
예시:
[10, 20, 30].forEach((num, idx) => {
console.log(num, idx);
});
forEach가 언제, 몇 번, 어떤 인자(num, idx)를 넘길지 모두 결정한다.forEach에게 이양된다.비동기 처리 구조의 핵심
자바스크립트는 단일 스레드 기반이라,
비동기 작업(setTimeout,fetch,addEventListener)을 효율적으로 처리하기 위해 콜백 기반의 제어권 이양이 필수적이다.
추상화와 재사용성 향상
로직을 외부에서 주입(콜백)받아 실행 시점을 제어함으로써,
함수의 범용성과 유연성이 높아진다.
고차 함수(Higher-Order Function)의 기반
함수를 인자로 전달하고 실행을 위임하는 개념은
함수형 프로그래밍과 리액트 렌더링 로직에도 직결된다.