자바스크립트를 쓰다 보면, 어느 순간 이런 코드를 작성하게 됩니다.
greet(); // "Hello World!"
function greet() {
console.log("Hello World!");
}선언 전에 호출했는데 잘 됩니다. 그런데 이렇게 바꾸면?
greet(); // TypeError: greet is not a function
var greet = function () {
console.log("Hello World!");
};에러가 납니다. console.log로 10분 디버깅하다가 에러 메시지를 읽었습니다. TypeError: greet is not a function. 함수인데 함수가 아니라고요.
자바스크립트가 저한테만 그러는 건 아니었더라고요. “왜 같은 함수인데 다르게 동작하지?” — 다들 한 번쯤 이런 생각 해보셨죠?

사진: Joan Gamell / Unsplash
함수 선언식과 함수 표현식은 생긴 건 비슷해도 동작 방식이 다릅니다. 그 차이의 핵심에 호이스팅(Hoisting) 이 있어요. 오늘은 이 개념을 실무 예제와 함께 제대로 정리해볼게요.
함수 선언식(Function Declaration)#
function 키워드로 시작하는 가장 기본적인 함수 형태입니다.
function add(a, b) {
return a + b;
}이름이 필수예요. function () {} 처럼 이름 없이는 쓸 수 없습니다.
함수 표현식(Function Expression)#
변수에 함수를 값처럼 할당하는 방식입니다.
const add = function (a, b) {
return a + b;
};여기서 오른쪽 function (a, b) { ... } 부분이 익명 함수 표현식입니다. 이름을 붙일 수도 있어요.
const add = function addNumbers(a, b) {
return a + b;
};이름을 붙이면 스택 트레이스에서 함수명이 표시되어 디버깅할 때 도움이 됩니다. 다만 addNumbers는 함수 내부에서만 참조할 수 있고, 외부에서는 add로만 호출해야 해요.

핵심 차이: 호이스팅#
이 둘의 가장 중요한 차이가 바로 호이스팅 동작입니다.
호이스팅이란 자바스크립트 엔진이 코드를 실행하기 전에 변수와 함수 선언을 스코프의 맨 위로 끌어올리는 동작을 말합니다. 실제로 코드가 이동하는 건 아니고, 엔진이 실행 컨텍스트를 준비하는 과정에서 일어나는 일이에요.
함수 선언식은 완전히 호이스팅됩니다#
// 선언 전에 호출해도 잘 동작합니다
console.log(add(2, 3)); // 5
function add(a, b) {
return a + b;
}자바스크립트 엔진은 코드를 실행하기 전에 함수 선언식 전체(이름 + 본문)를 미리 등록합니다. 그래서 선언 전에 호출해도 문제가 없어요.
내부적으로는 이렇게 해석됩니다.
// 엔진이 실제로 처리하는 순서 (개념적)
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
함수 표현식은 변수 선언만 호이스팅됩니다#
console.log(add); // undefined (var) 또는 ReferenceError (let/const)
console.log(add(2, 3)); // TypeError: add is not a function
var add = function (a, b) {
return a + b;
};var로 선언하면 변수 자체는 호이스팅되지만, 함수가 할당되는 건 코드 실행 시점입니다. 그래서 할당 전에 호출하면 undefined()를 실행하려다가 오류가 나는 거예요.
let이나 const는 더 엄격합니다. 호이스팅은 되지만 TDZ(Temporal Dead Zone) 때문에 선언 전에 접근하면 바로 ReferenceError가 납니다.
console.log(add); // ReferenceError: Cannot access 'add' before initialization
const add = function (a, b) {
return a + b;
};한눈에 비교#
| 구분 | 함수 선언식 | 함수 표현식 (var) | 함수 표현식 (let/const) |
|---|---|---|---|
| 호이스팅 | 완전히 (이름 + 본문) | 변수명만 (값은 undefined) | 변수명만 (TDZ) |
| 선언 전 호출 | ✅ 가능 | ❌ TypeError | ❌ ReferenceError |
| 이름 필수 여부 | 필수 | 선택 | 선택 |
표를 보고 “그럼 그냥 항상 함수 선언식 쓰면 되겠다"고 생각하셨나요? 저도 잠깐 그랬는데, 화살표 함수가 조용히 기다리고 있었습니다.
화살표 함수는 어디에?#
화살표 함수(=>)는 함수 표현식의 일종입니다. 호이스팅 동작도 동일해요.
// 이렇게 쓰면
const add = (a, b) => a + b;
// 이것과 동작이 같습니다
const add = function (a, b) {
return a + b;
};다만 화살표 함수는 일반 함수와 중요한 차이가 있어요.
this 바인딩이 다릅니다. 화살표 함수는 자신만의 this를 갖지 않고, 선언된 위치의 외부 스코프 this를 그대로 씁니다.
const counter = {
count: 0,
// 일반 함수 표현식: this는 counter 객체
increment: function () {
this.count++;
},
// 화살표 함수: this는 외부 스코프 (여기선 전역 또는 undefined)
incrementArrow: () => {
this.count++; // 의도한 대로 동작하지 않을 수 있습니다
},
};그래서 객체 메서드나 이벤트 핸들러에서 this가 필요한 경우에는 일반 함수 표현식을 써야 합니다.
arguments 객체가 없습니다. 화살표 함수는 arguments 객체를 갖지 않아요. 가변 인자를 처리할 때는 rest 파라미터(...args)를 사용하세요.
// 일반 함수: arguments 사용 가능
function sum() {
return Array.from(arguments).reduce((acc, val) => acc + val, 0);
}
// 화살표 함수: rest 파라미터 사용
const sum = (...args) => args.reduce((acc, val) => acc + val, 0);실무에서 어떻게 쓸까?#
이론은 알겠는데, 실제로 어떤 상황에서 뭘 쓸지 헷갈리죠. 몇 가지 기준을 공유해드릴게요.
콜백과 짧은 함수는 화살표 함수#
map, filter, forEach 같은 배열 메서드의 콜백에는 화살표 함수가 자연스럽습니다. 코드가 짧고 읽기 좋아요.
const numbers = [1, 2, 3, 4, 5];
// 화살표 함수가 훨씬 간결합니다
const doubled = numbers.map((n) => n * 2);
const evens = numbers.filter((n) => n % 2 === 0);모듈 레벨 함수는 취향껏, 단 일관성 있게#
최근 프론트엔드 코드에서는 const로 선언하는 함수 표현식을 많이 씁니다. 특히 React 컴포넌트나 유틸 함수를 작성할 때 자주 보게 되죠.
// 많은 프로젝트에서 볼 수 있는 스타일
const formatDate = (date) => {
return new Intl.DateTimeFormat("ko-KR").format(date);
};
export default function UserCard({ name }) {
return <div>{name}</div>;
}어느 쪽이 더 좋다기보다는, 팀의 컨벤션을 따르고 일관성을 유지하는 게 중요합니다.
호이스팅에 의존하는 코드는 피하세요#
함수 선언식이 호이스팅된다는 사실을 알고, 선언보다 위에서 호출하는 코드를 일부러 작성하는 경우가 있습니다. 하지만 이건 코드를 읽는 사람 입장에서 혼란스러울 수 있어요.
// ❌ 가능은 하지만, 읽는 사람이 아래로 스크롤해야 합니다
initApp();
function initApp() {
// ...
}
// ✅ 선언 후 호출하는 게 의도가 더 명확합니다
function initApp() {
// ...
}
initApp();물론 파일 구조상 초기화 코드를 위에 두고 헬퍼 함수들을 아래에 두는 방식도 하나의 스타일입니다. 중요한 건 팀원 모두가 이해하고 합의한 방식을 따르는 것이에요.
정리#
- 함수 선언식: 완전 호이스팅 → 선언 전 호출 가능. 이름 필수.
- 함수 표현식: 변수만 호이스팅 → 선언 전 호출 불가. 이름 선택.
- 화살표 함수: 함수 표현식의 일종.
this와arguments가 없음. - 실무 팁: 호이스팅에 의존하는 코드보다는 선언 후 사용하는 방식이 읽기 좋습니다.
매일 쓰는 함수지만, 동작 원리를 알면 디버깅도 훨씬 빨라집니다. 다음에 TypeError: xxx is not a function을 만났을 때 바로 호이스팅을 확인해보세요.
