「한 달 동안 자바스크립트 박살내기」는, ChatGPT쨩이 선생님이 되어, 매일 수업해주는 느낌으로 진행합니다.
GPT쨩의 말은 이렇게 작성합니다.
오늘 공부 내용 및 목표
오늘 학습 목표
오늘은 JavaScript에서 함수를 단순한 코드 묶음이 아니라 ‘값’으로 다루는 감각을 잡는 날이야. TypeScript를 써봤다면 함수 선언 자체는 익숙할 텐데, 오늘 중요한 건 문법 암기보다 함수를 변수에 넣는다 → 다른 함수에 넘긴다 → 받은 쪽에서 호출한다는 흐름이야.
특히 callback은 앞으로 이벤트, 배열 메서드, 비동기 처리, 레거시 프론트엔드 코드를 읽을 때 계속 나오니까 오늘 확실히 기반을 잡아두자.
C언어로 프로그래밍 공부를 시작해서 그런지, 함수 포인터라는 표현이 더 편하긴 한데 JS에서는 콜백이라는 용어에 익숙해질 필요가 있을 것 같다.
일이 늦게 끝나고 이것저것 하나보니 1시가 됐는데 그래도 자바스크립트 개발살내기 프로젝트는 멈추지 않는다.
오늘의 위치
단계: JavaScript 핵심
주제: 함수 — declaration / expression / arrow / parameter / return / 함수가 값이라는 개념 / callback 기초
필수 예상시간: 약 90분 선택 심화: 15분
오늘 끝나면 할 수 있어야 하는 것
함수 선언문, 함수 표현식, arrow function을 읽고 기본적인 차이를 설명할 수 있다.
parameter, argument, return이 코드 흐름에서 어떤 역할을 하는지 추적할 수 있다.
JavaScript에서 함수가 값이라는 말을 코드로 설명할 수 있다. callback을 다른 함수에 전달하고, 언제 호출되는지 추적할 수 있다.
라고 한다. 공부 끝나면 3시 다 되겠구나.
어제 내용 복습
복습 1
const level = 0;
if (level) {
console.log("level exists");
}
왜 level은 분명 number인데도 로그가 출력되지 않을까?
level은 0인데, 0은 falsy라 if 조건문 안으로 못들어가기 때문.
정답.
복습 2
const character = {
level: 10,
};
character.level이 number인지 확인하려면 어떤 조건식을 쓰면 될까?
아래와 같이 쓰면 되지 않을까 싶다.
if(typeof level === "number"){
// 내용
}
정답. 인데 character.level로 쓰는걸 깜빡함.
복습 3
const character = {
hp: 100,
};
character.hp = 50;
character가 const인데도 왜 hp는 바꿀 수 있을까?
const는 그 변수 자체의 재할당을 못하게 막는거고, 내부 프로퍼티는 변경이 가능하다.
정답.
핵심 수업
함수는 입력을 받아 일을 하고, 필요하면 값을 돌려주는 코드다
가장 익숙한 형태부터 보자.
function calculateDamage(attack, defense) {
const damage = attack - defense;
return damage;
}
const result = calculateDamage(30, 10);
console.log(result); // 20
음, 공격력과 방어력을 가지고 데미지를 계산하는 함수다.
여기서, attack, defense는 parameter(매개변수)야. 함수가 호출될 때 값을 받아둘 이름이라고 보면 돼.
30, 10은 argument(인수)야. 실제 호출 시 전달한 값이지.
그리고, return에는 두 역할이 있어.
함수를 그 자리에서 끝낸다. 호출한 곳에 값을 돌려준다.
그래서,
const result = calculateDamage(30, 10);
는, 계산이 끝난 뒤,
const result = 20;
처럼 생각할 수 있어.
아주 기본적인 내용이다. 넘어가자.
return을 만나면 함수는 즉시 끝난다
function getDamage(attack) {
if (attack <= 0) {
return 0;
}
console.log("damage calculation");
return attack * 2;
}
여기서, getDamage(0) 을 호출하면 첫 번째 return 0 에서 함수가 끝나므로 아래 console.log 까지 내려가지 않아.
당연히 아는 내용이다.
그리고 명시적으로 값을 return하지 않은 함수의 반환값은 undefined야.
function logAttack() {
console.log("attack");
}
const result = logAttack();
console.log(result); // undefined
오호. 이건 몰랐던 내용이다.
값을 명시적으로 반환하지 않는 함수의 반환값은 undefined.
함수 선언문과 함수 표현식
지금까지 쓴 건 함수 선언문(function declaration)이야.
function attack() {
console.log("attack");
}
이건 이미 익숙한 형태이다.
JavaScript에서는 함수를 만들어서 변수에 넣을 수도 있어.
const attack = function () {
console.log("attack");
};
이건 함수 표현식(function expression)이야.
음... 람다식같은거같다.
여기서 중요한 건 단순히 문법이 다르다는 사실보다 이거야.
const attack = function () { ... };
오른쪽의 함수가 하나의 **값**이고, 그 값을 `attack`이라는 변수에 넣고 있다는 것. 숫자를 변수에 넣듯, 함수도 넣을 수 있어.
그리고 변수에 들어 있는 함수는 이렇게 실행해.
action();
함수 선언문과 형태는 비슷한데, 함수 이름을 변수로 쓰는 느낌인거같다.
Arrow function
함수 표현식을 더 짧게 쓰는 문법이 arrow function이야.
// 함수 표현식
const attack = function (damage) {
return damage * 2;
};
// Arrow function
const attack = (damage) => {
return damage * 2;
};
// Arrow function - 패러미터가 하나면 괄호를 생략
const attack = damage => {
return damage * 2;
};
// Arrow function - 함수 본문이 표현식 하나를 반환하는 것 뿐이라면 return과 중괄호를 생략
const attack = damage => damage * 2;
아, 이거 자바의 정석에도 나왔던거같다.
람다식같다고 했는데 람다식 맞다.
여기서 주의할 점은 {}를 쓰면 자동 return이 사라진다는 거야.
const attack = damage => {
damage * 2;
};
console.log(attack(10)); // undefined
이건 주의해야겠다. 중괄호를 쓰면 return도 무조건 써야한다.
함수도 값이다
function attack() {
console.log("attack!");
}
const action = attack;
마지막 줄에서:
const action = attack;
우리는 attack을 실행하지 않았어. 왜냐하면 ()가 없기 때문이야.
attack은 함수 자체, attack()은 그 함수를 지금 호출해서 얻은 결과야.
이 차이는 중요한거같다. 위 코드에서, action()으로 attack()을 실행할 수 있는 것도, 함수 자체를 저장해두었기 때문이다.
const actionA = attack;
const actionB = attack();
첫 번째는 함수 자체를 저장해.
두 번째는 attack을 즉시 실행하고 그 반환값을 actionB에 저장해.
예를 들어 :
function getDamage() {
return 20;
}
const a = getDamage;
const b = getDamage();
console.log(typeof a); // "function"
console.log(b); // 20
a에는 함수 자체가, b는 함수 반환값인 20이 저장된다.
typeof로 확인하면 a는 "function"으로 출력된다고 한다.
function이라는 타입이 따로 있는거구나.
함수를 다른 함수에 넘길 수 있다
함수가 값이라면 당연히 argument로도 전달할 수 있어.
function attack() {
console.log("공격!");
}
function executeAction(action) {
action();
}
executeAction(attack);
attack()은 attack 함수의 반환값을 의미하지만, attack은 attack 함수 자체를 의미한다.
그래서, executeAction(attack)에서, 인수로 attack 함수 자체를 넘기고 있고, executeAction 함수에서는 인수로 전달된 것을 실행한다.
그래서, 콘솔창엔 "공격!"이라고 출력될 것.
Callback은 뭐냐
다른 함수에 전달되어, 그 함수 안에서 호출되는 함수**를 callback이라고 생각하면 돼.
function executeAction(callback) {
console.log("행동 시작");
callback();
console.log("행동 종료");
}
function attack() {
console.log("공격!");
}
executeAction(attack);
출력은 "행동 시작" -> "공격!" -> "행동 종료" 가 될 것이다.
여기서 attack이 callback이야.
Callback이라고 해서 반드시 비동기인 건 아니야. 위 코드는 executeAction 안에서 바로 callback을 호출하니까 동기 callback이야.
비동기 콜백이라는게 따로 있는 모양이다.
나중에 브라우저 이벤트에서는:
button.addEventListener("click", attack);
처럼 “클릭이 일어나면 이 함수를 호출해줘”라는 형태가 되고, 배열 메서드에서도:
numbers.map(callback)
같은 구조가 나와.
오늘은 아직 addEventListener나 map을 공부하는 날이 아니니까 API 자체는 외울 필요 없어. 중요한 건 함수 자체를 넘겨두고, 받은 쪽이 적절한 시점에 호출할 수 있다는 구조야.
아무튼, 함수 자체를 넘겨주고, 그걸 다른 함수 내에서 호출하는 구조가 있다는 의미인 것 같다.
가장 흔한 실수 : callback과 callback()
이 둘은 전혀 달라.
executeAction(attack);
은 attack 함수 자체를 넘겨.
executeAction(attack());
은 먼저 attack()을 실행해버리고, 그 반환값을 executeAction에 넘겨.
아까 함수 그 자체와 함수값을 구별할 때와 동일하다.
function attack() {
console.log("공격!");
}
function executeAction(callback) {
callback();
}
executeAction(attack());
실행 흐름은:
1. attack()을 먼저 실행 → 공격! 출력
2. attack은 return이 없으므로 결과는 undefined
3. 결국 executeAction(undefined)가 됨
4. 함수 내부에서 undefined()를 호출하려 해서 오류
즉 callback을 넘길 때는 보통 함수 이름에 ()를 붙이지 않아.
callback()은, callback이 함수일 때만 호출 가능하다.
function 이외의 타입인 경우에는 오류가 나는 모양이다.
코드 보고 동작 예측하기
예측 1
function getDamage() {
return 30;
}
const a = getDamage;
const b = getDamage();
console.log(typeof a);
console.log(b);
function, 30이 출력된다.
a는 getDamage 함수 자체가, b는 getDamage의 리턴값, 즉 30이 저장되기 때문.
정답.
예측 2
const double = value => {
value * 2;
};
console.log(double(5));
함수 표현식이긴 한데, 중괄호가 있음에도 return이 없기 때문에 undefined가 된다.
그러므로, undefined가 출력된다.
정답.
예측 3
function attack() {
console.log("B");
}
function execute(callback) {
console.log("A");
callback();
console.log("C");
}
execute(attack);
A -> B -> C 순으로 출력된다.
execute 함수의 인수로 attack이라는 함수 자체가 콜백으로 전달되고 있고, execute 함수 내에서 A를 출력한 뒤 attack()을 실행하고, 그 후에 C를 출력하고 있게 때문.
정답.
예측 4
function getAction() {
return function () {
console.log("attack");
};
}
const action = getAction();
action();
getAction()의 반환값은 어떤 타입일지도 같이 생각해봐.
action에는 getAction의 함수값이 들어가는데, getAction의 함수값은 또 함수 표현식인데, 중괄호가 있는데 return이 없다.
그러니 getAction의 함수값은 undefined. 그러니 action에 저장되는 값도 undefined.
코드 마지막에 undefined타입을 실행하려 하고 있으니 오류가 난다.
오답. getAction는 함수 표현식 자체를 반환한다. 함수가 action에 전달되기 때문에 action()을 실행하면 console.log("attack")이 실행된다.
직접 코딩
전투 행동 실행기 만들기
오늘은 같은 실행기에 여러 행동 함수를 넘길 수 있게 만들어보자.
시작 코드는 이거야.
const player = {
name: "Djeeta",
hp: 100,
mp: 0,
};
const enemy = {
name: "Goblin",
hp: 50,
};
function attack(player, enemy) {
// TODO: enemy의 hp를 10 감소
// TODO: "Djeeta attacks Goblin" 형태로 출력
}
function charge(player) {
// TODO: player의 mp를 20 증가
// TODO: "Djeeta charges MP" 형태로 출력
}
function executeAction(action, player, target) {
// TODO: action 함수를 여기서 호출
}
executeAction(attack, player, enemy);
executeAction(charge, player);
console.log(player);
console.log(enemy);
executeAction은 어떤 행동인지 직접 알 필요가 없고, 전달받은 함수 action을 실행하는 역할만 하게 만들어봐.
그리고 기본 버전을 완성했다면 executeAction이 행동 전후에 로그를 찍도록 바꿔봐.
--- action start ---
Djeeta attacks Goblin
--- action end ---
아래와 같이 구현해주었다.
const player = {
name: "Djeeta",
hp: 100,
mp: 0,
};
const enemy = {
name: "Goblin",
hp: 50,
};
function attack(player, enemy) {
enemy.hp -= 10;
console.log(player.name + " attacks " + enemy.name);
}
function charge(player) {
player.mp += 20;
console.log(player.name + " charges MP");
}
function executeAction(action, player, target) {
console.log("--- action start ---")
action(player, target);
console.log("--- action end --- ")
}
executeAction(attack, player, enemy);
executeAction(charge, player);
console.log(player);
console.log(enemy);

성공!
charge()의 경우엔 인수를 하나만 받는데도, executeAction 함수 내에서 콜백 함수에 인수 두 개를 집어넣어도 문제가 없는게 신기하다.
코드 읽기 / 디버깅 드릴
아래 코드는 공격을 등록해두고 나중에 실행하려는 코드야. 그런데 의도대로 동작하지 않아.
function attack() {
console.log("attack!");
return 10;
}
function registerAction(callback) {
console.log("registered");
callback();
}
const selectedAction = attack();
registerAction(selectedAction);
질문은 세 개야.
1. 실제로 어떤 순서로 실행될까?
2. 왜 registerAction 안에서 오류가 날까?
3. attack을 미리 실행하지 않고 함수 자체를 넘기려면 어느 줄을 어떻게 바꿔야 할까?
- attack()이 실행되고, registerAction()이 실행된다.
- selectedAction에 attack()의 반환값인 10이 저장되는데, registerAction의 callback() 부분에서 인수를 실행하고 있어, 함수가 아닌 인수를 실행하려 하므로 에러가 난다.
- selectedAction에 attack()이 아니라 attack을 대입해야 한다.
전부 정답.
종료 테스트
1번 문제
다음 두 표현의 차이를 설명해봐.
attack
attack()
전자는 함수 자체, 후자는 함수의 반환값.
정답.
2번 문제
function heal(amount) {
return amount + 10;
}
const result = heal(20);
console.log(result);
무엇이 출력되고, amount와 20은 각각 parameter와 argument 중 무엇일까?
30이 출력된다. amount는 parameter, 20은 argument.
정답.
3번 문제
아래 함수가 12를 반환하도록 가장 간단하게 고쳐봐.
const double = value => {
value * 2;
};
console.log(double(6));
다음과 같이 고치면 될 것 같다.
const double = value => {
return value * 2;
};
console.log(double(6));
두 줄의 출력을 예상하고, createAction("attack")을 호출한 직후 action 변수에 무엇이 들어 있는지 설명해봐.
정답.
4번 문제
function execute(callback) {
console.log("start");
callback();
console.log("end");
}
function skill() {
console.log("skill");
}
execute(skill);
출력 순서를 쓰고, 이 코드에서 callback은 어떤 함수인지 설명해봐.
callback은 skill()이다. 출력 순서는, start -> skill -> end.
정답.
5번 문제
function createAction(name) {
return function () {
return name;
};
}
const action = createAction("attack");
const result = action();
console.log(typeof action);
console.log(result);
두 줄의 출력을 예상하고, createAction("attack")을 호출한 직후 action 변수에 무엇이 들어 있는지 설명해봐.
function, attack이 출력된다.
action 변수엔, attack을 반환하는 함수 자체가 들어가있다.
정답.
감상
아직은 꽤나 간단하고 이미 아는 내용이 많긴 한데, 내 지식에 빵꾸난 부분도 꽤 많다는 것을 느낀다.
자러가자!
'웹 개발 > 프론트엔드 - JavaScript' 카테고리의 다른 글
| [JavaScript] 한 달 동안 자바스크립트 박살내기 (5) - 객체와 참조 (0) | 2026.08.27 |
|---|---|
| [JavaScript] 한 달 동안 자바스크립트 박살내기 (4) - scope / hoisting / TDZ (1) | 2026.08.26 |
| [JavaScript] 한 달 동안 자바스크립트 박살내기 (2) - JS 실행환경 / 기본 (0) | 2026.08.23 |
| [JavaScript] 한 달 동안 자바스크립트 박살내기 (1) - 프로젝트 시작 (0) | 2026.08.23 |
| [JavaScript] 한 달 동안 자바스크립트 박살내기 (0) - 왜 하는가? (0) | 2026.08.22 |