콜백(callback)은 다른 함수에 넘겨 그 함수가 부르게 하는 함수입니다. 함수도 값이라 인자로 넘길 수 있고, 넘겨받은 함수가 정한 때에 정한 만큼 부릅니다. 넘길 때는 함수 이름에 괄호를 붙이지 않습니다.
같은 반복, 같은 차례에서 하는 일만 바꾸고 싶을 때가 있습니다. 그 일을 함수로 만들어 넘기면 받는 쪽 코드는 그대로 두고 하는 일만 바꿀 수 있습니다. 7단원의 배열 메서드와 12단원의 단추 누르기가 모두 이 꼴입니다. 이 레슨에서는 함수를 넘기면 누가 언제 부르는지 봅니다.
1함수를 넘기면 받은 함수가 부른다
그림의 코드입니다.
function hello() {
console.log('안녕');
}
function run(action) {
action();
}
run(hello);
안녕7번 줄은 hello에 괄호를 붙이지 않고 run에 넘깁니다. run을 부르면 새 칸이 생기고, 매개변수 action이 hello와 같은 함수에 붙습니다. 함수 하나에 이름표가 hello와 action 둘입니다(규칙 3).
5번 줄 action();은 action이 가리키는 함수를 부릅니다. 그래서 hello의 칸이 run의 칸 아래에 쌓이고, 2번 줄이 안녕을 찍습니다. hello를 부른 것은 7번 줄이 아니라 run 안의 5번 줄입니다.
이렇게 넘겨서 받은 쪽이 부르게 하는 함수를 콜백이라고 합니다. 여기서는 hello가 run의 콜백입니다.
2몇 번, 언제 부를지는 받은 함수가 정한다
이번에는 받은 함수를 두 번 부르는 twice에 hello를 넘깁니다.
function hello() {
console.log('안녕');
}
function twice(action) {
action();
action();
}
console.log('시작');
twice(hello);
console.log('끝');
시작
안녕
안녕
끝9번 줄은 hello를 건넬 뿐이고, hello는 twice의 5번 줄과 6번 줄이 부를 때마다 한 번씩 실행됩니다. 그래서 안녕이 두 번 찍히고, twice가 끝난 뒤에야 10번 줄이 끝을 찍습니다. 넘기는 쪽은 함수를 건네고, 그 함수를 언제 몇 번 부를지는 받은 함수가 정합니다.
3넘길 때 괄호를 붙이면
2절 코드에서 시작과 끝을 찍는 두 줄을 빼고, hello 뒤에 괄호를 붙여 twice(hello());로 넘깁니다. 무엇이 찍힐까요? 오류가 난다면 어디서 날지도 정한 뒤 펼쳐 보세요.
function hello() {
console.log('안녕');
}
function twice(action) {
action();
action();
}
twice(hello());
실행 결과 보기
안녕main.js:5 action(); ^ TypeError: action is not a function at twice (main.js:5:3) at main.js:8:1예상과 다르면 그곳이 배울 곳입니다.
8번 줄은 괄호 안의 hello()부터 계산합니다(규칙 2). 그 자리에서 hello를 불러 안녕이 한 번 찍히고, hello가 돌려준 undefined가 twice에 넘어갑니다. 그래서 action이 undefined에 붙고, 5번 줄이 undefined를 부르려다 멈춥니다.
TypeError 줄의 action is not a function은 'action은 함수가 아니다', 곧 함수 대신 함수가 돌려준 값을 넘겼다는 뜻입니다. 그 아래 at 줄은 오류가 twice 안의 5번 줄에서 났고, twice를 부른 것이 8번 줄이라고 알려 줍니다.
괄호는 부르라는 표시라서, 넘길 때 붙이면 넘기기 전에 그 자리에서 불립니다. 함수를 넘기려면 이름만 씁니다.
예제
수 셋에 같은 일 하기
1부터 n까지 수마다 받은 함수를 부르는 each입니다. 넘기는 함수만 바꾸면 하는 일이 바뀝니다.
function each(n, action) {
for (let i = 1; i <= n; i++) {
action(i);
}
}
function guest(i) {
console.log(`${i}번 손님`);
}
function cup(i) {
console.log(i * 4000);
}
each(3, guest);
each(2, cup);
1번 손님
2번 손님
3번 손님
4000
8000each는 반복을 맡고, 수마다 할 일은 넘겨받은 함수가 맡습니다. 12번 줄은 guest를 넘겨 손님 번호를 부르고, 13번 줄은 같은 each에 cup을 넘겨 잔 수마다 값을 찍습니다. 3번 줄의 action(i)는 바퀴마다 i를 인자로 넘겨 콜백을 부릅니다.
7단원의 배열 메서드는 each처럼 반복을 맡고, 할 일만 콜백으로 받습니다.
과목 끝의 할 일 앱에서도 할 일 목록 그리기 · 저장하기 · 불러오기를 render()·save()·load() 같은 함수로 나눕니다. 단추를 누르면 할 일이 더해지게 하는 것도, 눌릴 때 부를 함수를 단추에 넘기는 일입니다.
자주 틀리는 곳
틀린 생각: 함수를 넘길 때도 이름 뒤에 괄호를 붙여야 한다.
함수를 쓸 때는 늘 괄호를 붙여 불렀으니, 넘길 때도 붙여야 할 것 같습니다. 3절처럼 괄호를 붙이면 그 자리에서 불려, 함수가 아니라 함수가 돌려준 값이 넘어갑니다.
맞는 규칙: 괄호는 부르라는 표시입니다. 함수를 넘기려면 twice(hello)처럼 이름만 쓰고, 부르는 일은 받은 함수에 맡깁니다(규칙 5).
틀린 생각: 넘긴 함수는 넘기는 그 순간 한 번 불린다.
넘기는 줄에 함수 이름이 보이니 그 줄에서 실행될 것 같습니다. 2절의 twice는 넘겨받은 hello를 두 번, 자기 몸의 5번 줄과 6번 줄에서 불렀습니다.
맞는 규칙: 넘기는 쪽은 함수를 건넬 뿐이고, 언제 몇 번 부를지는 받은 함수가 정합니다.
틀린 생각: 콜백 안의 이름은 그 콜백을 부른 함수의 칸에서 찾는다.
hello를 부른 것이 run이니, hello 안의 이름도 run 칸에서 찾을 것 같습니다.
const shop = '플로우 카페';
function hello() {
console.log(shop);
}
function run(action) {
const shop = '다른 가게';
action();
}
run(hello);
플로우 카페hello 칸에는 shop이 없어, hello가 만들어진 전역 칸에서 찾습니다. run 칸의 shop은 hello가 찾는 차례에 들지 않습니다.
맞는 규칙: 콜백도 함수라 이름은 그 칸에서 찾고, 없으면 부른 칸이 아니라 콜백이 만들어진 칸으로 나가며 찾습니다(규칙 7).
확인 문제
1. 예제 코드의 13번 줄을 each(3, cup);으로 바꿨습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
function each(n, action) {
for (let i = 1; i <= n; i++) {
action(i);
}
}
function guest(i) {
console.log(`${i}번 손님`);
}
function cup(i) {
console.log(i * 4000);
}
each(3, guest);
each(3, cup);
힌트
13번 줄의 each는 i가 1, 2, 3일 때마다 3번 줄에서 cup을 부릅니다.
정답
모두 6줄입니다.
1번 손님
2번 손님
3번 손님
4000
8000
1200012번 줄이 손님 번호 셋을 찍고, 13번 줄의 each가 cup을 세 번 불러 4000 · 8000 · 12000을 찍습니다.
흔한 오답은 마지막에 12000만 한 줄 찍는 것입니다. 넘긴 cup이 한 번만 불린다고 본 것입니다. 몇 번 부를지는 받은 each가 정합니다.
2. 예제의 each로 손님 번호 셋을 부르려 합니다. 빈칸을 채우세요.
function each(n, action) {
for (let i = 1; i <= n; i++) {
action(i);
}
}
function guest(i) {
console.log(`${i}번 손님`);
}
each(3, ____);
힌트
each의 3번 줄이 action에 괄호를 붙여 부릅니다. 빈칸에는 부를 함수 자체가 들어갑니다.
정답
guest입니다. 괄호 없이 이름만 씁니다.
흔한 오답은 guest()입니다. 9번 줄에서 guest를 먼저 불러 undefined번 손님이 한 번 찍히고, guest가 돌려준 undefined가 넘어가 3번 줄에서 TypeError로 멈춥니다(action is not a function). 넘길 때도 괄호를 붙인다는 생각입니다(규칙 5).
3. 알람을 울리는 함수를 넘겼습니다. 무엇이 찍힐까요? 마지막 줄까지 글자 그대로 쓰세요.
function later(action) {
console.log('준비');
action();
console.log('끝');
}
function ring() {
console.log('따르릉');
}
later(ring);
힌트
9번 줄부터 따라가며, later 안에서 action이 불리는 줄이 몇 번째인지 봅니다.
정답
모두 3줄입니다.
준비
따르릉
끝9번 줄은 ring을 건네기만 합니다. later의 2번 줄이 준비를 찍고, 3번 줄이 ring을 불러 따르릉을 찍은 뒤, 4번 줄이 끝을 찍습니다.
흔한 오답은 따르릉이 맨 위에 오는 답입니다. 넘기는 순간 불린다고 본 것입니다. 따르릉이 맨 아래에 오는 답은 넘긴 함수는 늘 나중에 돈다고 본 것입니다. 받은 함수는 자기 몸에서 부르는 그 자리에서 콜백을 실행합니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
1절 코드(일곱 줄)를 main.js에 저장해 돌립니다. 그다음 7번 줄을 run(hello());로 바꾸면 무엇이 찍히고 어디서 멈출지 먼저 적고, 바꿔 돌려서 맞춰 봅니다.
저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.