# 함수 선언식과 표현식, 제대로 알고 쓰기: 호이스팅까지 한 번에

> 함수 선언식과 표현식의 차이, 호이스팅 동작 원리, 화살표 함수까지. 매일 쓰지만 헷갈렸던 것들을 실무 예제와 함께 한 번에 정리합니다.

**Published:** 2026-04-04 | **Updated:** 2026-04-04

---


자바스크립트를 쓰다 보면, 어느 순간 이런 코드를 작성하게 됩니다.

```javascript
greet(); // "Hello World!"

function greet() {
  console.log("Hello World!");
}
```

선언 전에 호출했는데 잘 됩니다. 그런데 이렇게 바꾸면?

```javascript
greet(); // TypeError: greet is not a function

var greet = function () {
  console.log("Hello World!");
};
```

에러가 납니다. `console.log`로 10분 디버깅하다가 에러 메시지를 읽었습니다. `TypeError: greet is not a function`. 함수인데 함수가 아니라고요.

자바스크립트가 저한테만 그러는 건 아니었더라고요. "왜 같은 함수인데 다르게 동작하지?" — 다들 한 번쯤 이런 생각 해보셨죠?

{{< img src="images/contents/code-on-screen.jpg" alt="어두운 모니터에 표시된 코드 화면 - 매일 쓰는 함수도 동작 원리를 알면 다르게 보입니다" caption="사진: <a href='https://unsplash.com/ko/@gamell' target='_blank' title='새 창에서 열림'>Joan Gamell</a> / <a href='https://unsplash.com/ko' target='_blank' title='새 창에서 열림'>Unsplash</a>" >}}

함수 선언식과 함수 표현식은 생긴 건 비슷해도 동작 방식이 다릅니다. 그 차이의 핵심에 **호이스팅(Hoisting)** 이 있어요. 오늘은 이 개념을 실무 예제와 함께 제대로 정리해볼게요.

## 함수 선언식(Function Declaration)

`function` 키워드로 시작하는 가장 기본적인 함수 형태입니다.

```javascript
function add(a, b) {
  return a + b;
}
```

이름이 필수예요. `function () {}` 처럼 이름 없이는 쓸 수 없습니다.

## 함수 표현식(Function Expression)

변수에 함수를 값처럼 할당하는 방식입니다.

```javascript
const add = function (a, b) {
  return a + b;
};
```

여기서 오른쪽 `function (a, b) { ... }` 부분이 **익명 함수 표현식**입니다. 이름을 붙일 수도 있어요.

```javascript
const add = function addNumbers(a, b) {
  return a + b;
};
```

이름을 붙이면 스택 트레이스에서 함수명이 표시되어 디버깅할 때 도움이 됩니다. 다만 `addNumbers`는 함수 내부에서만 참조할 수 있고, 외부에서는 `add`로만 호출해야 해요.

{{< img src="images/contents/hoisting-concept.png" alt="코드 블록이 위로 끌어올려지는 과정 - 호이스팅은 선언이 스코프 맨 위로 올라가는 동작을 말합니다" >}}

## 핵심 차이: 호이스팅

이 둘의 가장 중요한 차이가 바로 **호이스팅** 동작입니다.

호이스팅이란 자바스크립트 엔진이 코드를 실행하기 전에 변수와 함수 선언을 스코프의 맨 위로 끌어올리는 동작을 말합니다. 실제로 코드가 이동하는 건 아니고, 엔진이 실행 컨텍스트를 준비하는 과정에서 일어나는 일이에요.

### 함수 선언식은 완전히 호이스팅됩니다

```javascript
// 선언 전에 호출해도 잘 동작합니다
console.log(add(2, 3)); // 5

function add(a, b) {
  return a + b;
}
```

자바스크립트 엔진은 코드를 실행하기 전에 함수 선언식 전체(이름 + 본문)를 미리 등록합니다. 그래서 선언 전에 호출해도 문제가 없어요.

내부적으로는 이렇게 해석됩니다.

```javascript
// 엔진이 실제로 처리하는 순서 (개념적)
function add(a, b) {
  return a + b;
}

console.log(add(2, 3)); // 5
```

### 함수 표현식은 변수 선언만 호이스팅됩니다

```javascript
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`가 납니다.

```javascript
console.log(add); // ReferenceError: Cannot access 'add' before initialization

const add = function (a, b) {
  return a + b;
};
```

### 한눈에 비교

| 구분 | 함수 선언식 | 함수 표현식 (var) | 함수 표현식 (let/const) |
|------|------------|-----------------|----------------------|
| 호이스팅 | 완전히 (이름 + 본문) | 변수명만 (값은 undefined) | 변수명만 (TDZ) |
| 선언 전 호출 | ✅ 가능 | ❌ TypeError | ❌ ReferenceError |
| 이름 필수 여부 | 필수 | 선택 | 선택 |

표를 보고 "그럼 그냥 항상 함수 선언식 쓰면 되겠다"고 생각하셨나요? 저도 잠깐 그랬는데, 화살표 함수가 조용히 기다리고 있었습니다.

## 화살표 함수는 어디에?

화살표 함수(`=>`)는 **함수 표현식의 일종**입니다. 호이스팅 동작도 동일해요.

```javascript
// 이렇게 쓰면
const add = (a, b) => a + b;

// 이것과 동작이 같습니다
const add = function (a, b) {
  return a + b;
};
```

다만 화살표 함수는 일반 함수와 중요한 차이가 있어요.

**`this` 바인딩이 다릅니다.** 화살표 함수는 자신만의 `this`를 갖지 않고, 선언된 위치의 외부 스코프 `this`를 그대로 씁니다.

```javascript
const counter = {
  count: 0,
  // 일반 함수 표현식: this는 counter 객체
  increment: function () {
    this.count++;
  },
  // 화살표 함수: this는 외부 스코프 (여기선 전역 또는 undefined)
  incrementArrow: () => {
    this.count++; // 의도한 대로 동작하지 않을 수 있습니다
  },
};
```

그래서 객체 메서드나 이벤트 핸들러에서 `this`가 필요한 경우에는 일반 함수 표현식을 써야 합니다.

**`arguments` 객체가 없습니다.** 화살표 함수는 `arguments` 객체를 갖지 않아요. 가변 인자를 처리할 때는 rest 파라미터(`...args`)를 사용하세요.

```javascript
// 일반 함수: 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` 같은 배열 메서드의 콜백에는 화살표 함수가 자연스럽습니다. 코드가 짧고 읽기 좋아요.

```javascript
const numbers = [1, 2, 3, 4, 5];

// 화살표 함수가 훨씬 간결합니다
const doubled = numbers.map((n) => n * 2);
const evens = numbers.filter((n) => n % 2 === 0);
```

### 모듈 레벨 함수는 취향껏, 단 일관성 있게

최근 프론트엔드 코드에서는 `const`로 선언하는 함수 표현식을 많이 씁니다. 특히 React 컴포넌트나 유틸 함수를 작성할 때 자주 보게 되죠.

```javascript
// 많은 프로젝트에서 볼 수 있는 스타일
const formatDate = (date) => {
  return new Intl.DateTimeFormat("ko-KR").format(date);
};

export default function UserCard({ name }) {
  return <div>{name}</div>;
}
```

어느 쪽이 더 좋다기보다는, **팀의 컨벤션을 따르고 일관성을 유지하는 게 중요합니다.**

### 호이스팅에 의존하는 코드는 피하세요

함수 선언식이 호이스팅된다는 사실을 알고, 선언보다 위에서 호출하는 코드를 일부러 작성하는 경우가 있습니다. 하지만 이건 코드를 읽는 사람 입장에서 혼란스러울 수 있어요.

```javascript
// ❌ 가능은 하지만, 읽는 사람이 아래로 스크롤해야 합니다
initApp();

function initApp() {
  // ...
}

// ✅ 선언 후 호출하는 게 의도가 더 명확합니다
function initApp() {
  // ...
}

initApp();
```

물론 파일 구조상 초기화 코드를 위에 두고 헬퍼 함수들을 아래에 두는 방식도 하나의 스타일입니다. 중요한 건 팀원 모두가 이해하고 합의한 방식을 따르는 것이에요.

## 정리

- **함수 선언식**: 완전 호이스팅 → 선언 전 호출 가능. 이름 필수.
- **함수 표현식**: 변수만 호이스팅 → 선언 전 호출 불가. 이름 선택.
- **화살표 함수**: 함수 표현식의 일종. `this`와 `arguments`가 없음.
- **실무 팁**: 호이스팅에 의존하는 코드보다는 선언 후 사용하는 방식이 읽기 좋습니다.

매일 쓰는 함수지만, 동작 원리를 알면 디버깅도 훨씬 빨라집니다. 다음에 `TypeError: xxx is not a function`을 만났을 때 바로 호이스팅을 확인해보세요.

---

{{< faq >}}

## 이 시리즈의 다른 글

- [`Promise.all` vs `Promise.allSettled`, 실수하기 쉬운 차이]({{< relref "/posts/javascript-promise-methods" >}})

