자바스크립트 함수 선언식과 표현식, 제대로 알고 쓰기
함수 선언식과 표현식의 차이와 활용법
자바스크립트에서 함수를 선언하는 방식은 크게 두 가지로 나눌 수 있습니다. 바로 ‘함수 선언식’과 ‘함수 표현식’이죠. 겉보기에는 큰 차이가 없어 보이지만, 실제로는 호이스팅 시점, 실행 흐름, 스코프, this 바인딩 등 다양한 측면에서 서로 다르게 동작합니다. 특히 React나 Next.js를 사용하면서도 이 차이를 제대로 이해하지 못해 여러 문제에 부딪히는 경우가 많은데요.
이번 글에서는 함수 선언식과 함수 표현식의 차이를 구체적인 예제 코드와 함께 비교해 보고, 상황에 맞게 함수를 선언하는 방법에 대해서도 함께 살펴보겠습니다.
함수 선언식과 함수 표현식의 기본 형태
함수는 자바스크립트에서 가장 핵심적인 구조 중 하나입니다. 그런데 함수를 정의하는 방식에는 두 가지 주요 문법이 있다는 사실 알고 계셨나요? 우리가 일상적으로 사용하는 대부분의 함수는 이 둘 중 하나의 형태를 따릅니다. 먼저 각 문법이 어떻게 생겼고, 어떤 상황에서 사용되는지부터 차근차근 살펴보겠습니다.
1) 함수 선언식
함수 선언식(Function Declaration)은 다음과 같이 function 키워드로 이름을 붙여 선언하는 방식입니다. 이 방식은 문법적으로 독립적인 문장이기 때문에 자바스크립트 코드 어디서든 선언할 수 있고, 전체 코드 실행 전에 먼저 해석됩니다.
function greet() {
console.log("안녕하세요");
}
이러한 특성 덕분에 함수 선언식은 코드 최상단이나 모듈 범위에서 공통적으로 쓰이는 유틸성 함수나 초기화 함수를 작성할 때 자주 사용됩니다. 함수 이름이 명시되어 있기 때문에 디버깅 시 call stack에 잘 나타나는 장점도 있습니다.
2) 함수 표현식
함수 표현식(Function Expression)은 함수를 변수에 할당하는 방식입니다. 변수는 보통 const, let과 함께 사용되며, 블록 스코프에 묶여 해당 블록 안에서만 유효합니다.
const greet = function () {
console.log("안녕하세요");
};
또한 ES6 이후부터는 아래와 같은 화살표 함수를 활용한 표현식도 매우 흔하게 사용됩니다.
const greet = () => {
console.log("안녕하세요");
};
이 방식은 변수와 함수를 한 번에 선언할 수 있고, 콜백 함수나 일회성 함수, 컴포넌트 내부의 핸들러 등 상황에 따라 동적으로 함수가 사용될 때 매우 유용합니다. 다만 표현식의 특성상 정의되기 전에 사용할 수 없고, this 바인딩 방식도 다르기 때문에 그 차이를 명확히 이해하고 있어야 합니다.
호이스팅 차이
이제 두 함수 선언 방식이 실제 코드 실행에 어떤 영향을 미치는지를 비교해 보겠습니다. 자바스크립트는 스코프를 해석할 때 변수와 함수 선언을 호이스팅(끌어올림)합니다. 하지만 함수 선언식과 함수 표현식은 이 호이스팅 과정에서 매우 다르게 작동합니다. 이 차이는 코드의 실행 순서와 오류 발생 여부에 직결되므로, 반드시 짚고 넘어가야 할 핵심 포인트입니다.
1) 함수 선언식
함수 선언식은 함수 전체가 스코프의 최상단으로 끌어올려지는(hoisting) 특징을 가집니다. 따라서 함수가 정의되기 전에 호출해도 정상적으로 실행됩니다.

sayHi(); // "안녕하세요" 출력
function sayHi() {
console.log("안녕하세요");
}
자바스크립트 엔진은 실행 전에 코드를 한 번 훑으며 함수 선언 전체를 메모리에 등록합니다. 이 덕분에 선언 위치와 관계없이 함수를 호출할 수 있어, 선언적이고 직관적인 코드 흐름을 구성할 수 있습니다. 따라서 보통 전역 함수, 모듈 유틸 함수, 테스트용 함수 등에 많이 사용됩니다.
2) 함수 표현식
반면 함수 표현식은 변수 선언은 호이스팅되지만, 함숫값은 코드가 실제 실행되는 순간에야 메모리에 할당됩니다. 그래서 다음과 같이 작성하면 오류가 발생하게 됩니다.

sayHi(); // ReferenceError: Cannot access 'sayHi' before initialization
const sayHi = function () {
console.log("안녕하세요");
};
이런 동작은 자바스크립트를 처음 배우는 분들에게는 꽤 낯설게 느껴질 수 있습니다. 함수 표현식으로 선언한 변수는 코드 실행 전에 자바스크립트 엔진이 변수 선언만 기억하고, 값은 아직 준비되지 않은 상태입니다. 이 구간을 ‘일시적인 사각지대(TDZ, Temporal Dead Zone)’이라고 부르며, 이 안에서는 변수에 접근할 수 없습니다.
쉽게 말해, 코드의 위쪽에서 호출했지만 변수에 함수가 ‘아직 들어있지 않은 상태’이기 때문에, ReferenceError가 발생하는 것입니다. 따라서 함수 표현식은 반드시 정의된 이후에만 호출해야 하며, if문이나 함수 내부, 비동기 콜백 안 등 코드 흐름이 갈라지는 구조에서는 더욱 주의해서 사용해야 합니다.
함수 표현식의 다양한 활용
함수 표현식은 단순히 const greet = function () {}처럼 사용하는 것이 아니라, 이름을 붙이거나, 화살표 함수로 작성하거나, 객체 메서드 안에서 사용되는 등 다양한 방식으로 확장됩니다. 이 섹션에서는 함수 표현식을 실무에서 어떻게 더 유연하게 활용할 수 있는지 알아보겠습니다.
1) 이름이 있는 함수 표현식
함수 표현식에도 이름을 붙일 수 있습니다. 이렇게 하면 함수 내부에서 자기 자신을 참조할 수 있기 때문에 재귀 호출이 가능해지고, 디버깅 시에도 스택 트레이스에서 이름이 표시되어 더 유용합니다.
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // 함수 이름을 내부에서 참조 가능
};
다만 외부에서는 fact()라는 이름으로 접근할 수 없고, 변수명 factorial로만 접근할 수 있습니다. 이처럼 이름이 있는 함수 표현식은 재귀 호출이 가능할 뿐 아니라, 디버깅 과정에서 호출 스택에 함수 이름이 표시되어 문제를 파악하기 쉬워진다는 장점이 있습니다.
2) 화살표 함수 표현식
ES6부터 도입된 화살표 함수(arrow function)는 특히 콜백 함수나 이벤트 핸들러, 간단한 로직을 처리할 때 유용하게 사용됩니다. 문법이 간결할 뿐만 아니라, this 바인딩 방식이 다르다는 중요한 특징이 있습니다.
화살표 함수는 자신만의 this를 가지지 않고, 외부(상위) 스코프의 this를 그대로 물려받는다는 특징이 있습니다. 이 덕분에 setTimeout이나 이벤트 핸들러 안에서도 복잡한 this 조작 없이 원하는 객체를 참조할 수 있게 됩니다.
const timer = {
count: 0,
start() {
setInterval(() => {
this.count++;
console.log(this.count); // this는 timer 객체를 가리킵니다.
}, 1000);
},
};

위와 같은 코드에서 기존 방식이라면, function()에서 this가 window나 undefined를 참조했겠지만, 화살표 함수를 사용하면 이러한 문제가 자연스럽고 쉽게 해결될 수 있습니다.
3) 객체 메서드 정의 시 유의 사항
객체 내부에 메서드를 정의할 때도 함수 표현식은 많이 사용됩니다. 일반적으로는 축약형 메서드 문법을 사용하는 것이 깔끔하지만, 상황에 따라 화살표 함수와의 차이를 꼭 인지하고 있어야 합니다.
const user = {
name: "아무개",
// 축약형 메서드 문법
sayHi() {
console.log(this.name);
},
// 일반 함수 표현식 (this 바인딩됨)
sayHello: function () {
console.log(this.name);
},
// 화살표 함수 (this는 user가 아님)
sayBye: () => {
console.log(this.name); // undefined
},
};
화살표 함수는 객체의 메서드 정의에 적합하지 않습니다. 따라서 내부에서 this를 사용할 일이 있다면, 반드시 일반 함수나 축약형 문법을 사용해야 안전하기 때문에 주의해서 사용해야 합니다.
마치며
함수 선언식과 함수 표현식은 단순히 문법 구조만 다른 것이 아니라, 실행 시점, 호이스팅 방식, this 바인딩, 스코프 처리 등 자바스크립트의 실행 원리와 깊이 연결되어 있는 중요한 개념입니다. 이 둘의 차이를 제대로 이해하면 코드의 가독성을 높이고, 예기치 못한 오류를 방지하며, 유지보수 과정에서도 훨씬 수월하게 문제를 해결할 수 있습니다.
특히 복잡한 비동기 흐름이나 이벤트 기반 코드에서 함수 표현식과 화살표 함수의 특성을 정확히 알고 있다면, 의도한 대로 동작하는 코드를 더 쉽게 작성할 수 있습니다. 마찬가지로, 전역 유틸 함수나 선언적인 흐름이 필요한 경우에는 함수 선언식을 선택하는 것이 더 명확한 구조를 만들어줄 수 있습니다.
결국 중요한 것은 ‘어떤 문법이 더 낫다’라는 결론이 아니라, 맥락에 맞게 선택할 줄 아는 판단력입니다. 이번 글을 통해 함수 선언 방식에 대한 이해가 더욱 깊어졌기를 바랍니다.
©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.