JavaScript 함수 선언식 vs 함수 표현식: 차이점과 성능 최적화 전략

ClydeHan·2024년 9월 12일
8

JavaScript 함수 선언식과 함수 표현식

difference-between-function-declaration-and-function-expression

이미지 출처: javascript.plainenglish.io

자바스크립트에서 함수는 중요한 프로그래밍 구성 요소이다. 함수는 코드 재사용, 가독성 향상, 유지 보수성 증가 등 다양한 이점을 제공한다. 이번 포스트에서는 함수 선언식(Function Declaration)과 함수 표현식(Function Expression)에 대해 자세히 설명하고, 두 방식의 차이점과 활용법을 알아보자.


함수 선언식 (Function Declaration)

📌 정의 및 문법

함수 선언식function 키워드를 사용하여 함수 이름과 함께 함수를 선언하는 방식이다. 함수 선언식은 자바스크립트에서 가장 기본적인 함수 정의 방식으로, 함수 이름을 명시적으로 제공해야 한다. 문법은 다음과 같다:

function 함수이름(매개변수1, 매개변수2, ...) {
  // 실행할 코드
  return 반환값;
}

이 방식의 중요한 특징은 호이스팅이 발생한다는 점이다. 즉, 함수 선언부가 실제로 호출되는 위치보다 위로 끌어올려져, 함수 선언 전에 호출할 수 있다는 점이다.


📌 장점

  1. 호이스팅으로 인한 유연성: 함수 선언식은 코드 상단으로 끌어올려지기 때문에 함수 선언 위치에 상관없이 호출할 수 있다.
  2. 디버깅이 용이함: 명시적인 함수 이름을 사용하기 때문에, 디버깅할 때 오류 메시지나 콜스택에서 함수 이름이 명확하게 표시된다.
  3. 코드 가독성: 코드 흐름에서 함수가 미리 선언된 상태에서 호출되기 때문에, 코드의 읽기 흐름이 자연스럽고 이해하기 쉽다.
  4. 전역/지역 스코프 처리 용이: 함수 선언식은 코드 블록 안에 위치하더라도 전역 또는 지역 스코프에 맞춰 쉽게 관리된다.
  5. 모듈화와 재사용: 동일한 함수 이름으로 여러 군데에서 호출 가능하며, 재사용성이 높다.

📌 동작 원리: 호이스팅 (Hoisting)

자바스크립트의 함수 선언식은 변수 선언과 달리 전체 함수 본문이 코드 상단으로 끌어올려진다. 이를 호이스팅이라고 하며, 다음과 같은 코드에서 볼 수 있다.

console.log(greet('Alice')); // "Hello, Alice!"

function greet(name) {
  return `Hello, ${name}!`;
}

위의 코드는 함수가 선언되기 전에 호출되었음에도 불구하고 정상적으로 실행된다. 이는 함수 선언식이 메모리에 먼저 할당되기 때문이다.

이게 가능한 이유는 자바스크립트의 내부 알고리즘 때문이다. 자바스크립트는 스크립트를 실행하기 전, 준비단계에서 전역에 선언된 함수 선언문을 찾고, 해당 함수를 생성한다. 스크립트가 진짜 실행되기 전 "초기화 단계"에서 함수 선언 방식으로 정의한 함수가 생성되는 것이다.

스크립트는 함수 선언문이 모두 처리된 이후에서야 실행된다. 따라서 스크립트 어디서든 함수 선언문으로 선언한 함수에 접근할 수 있는 것이다.

즉, 함수 선언문, greet는 스크립트 실행 준비 단계에서 생성되기 때문에, 스크립트 내 어디에서든 접근할 수 있다.


📌 함수 선언식이 성능적으로 유리한 이유

함수 선언식은 자바스크립트 엔진이 코드를 최적화할 때 성능적으로 유리한 경우가 있다. 특히 함수 선언은 런타임 이전에 메모리에 할당되므로, 호출 시점에서 바로 실행될 수 있다. 하지만 이러한 성능 차이는 대부분의 경우 미미하다. 다만, 매우 복잡한 애플리케이션에서는 함수 선언식이 함수 표현식에 비해 성능적으로 조금 더 나은 결과를 낼 수 있다.

함수 선언식이 호이스팅(hoisting)을 통해 코드 실행 이전에 메모리에 미리 할당된다는 사실은 함수 호출 시 즉시 접근할 수 있게 해준다. 자바스크립트 엔진은 실행 전에 함수 선언을 미리 파악하고 메모리에 올리기 때문에, 런타임 중 함수를 호출할 때 별도의 메모리 할당 없이 바로 실행 가능하다.

자바스크립트는 JIT(Just-In-Time) 컴파일러를 사용하여 함수를 즉시 컴파일하고 실행하는데, 함수 선언식은 미리 컴파일된 상태로 메모리에 위치하기 때문에, 호출 시점에서 추가적인 메모리 할당이 발생하지 않는다. 특히, 함수가 재귀 호출이나 빈번한 호출이 필요한 경우, 이러한 미리 할당된 함수는 성능 최적화에 도움을 줄 수 있다.

자바스크립트 엔진의 V8 엔진(Chrome 및 Node.js에서 사용하는 엔진)은 이러한 최적화 과정을 적극적으로 수행한다. V8 엔진은 함수 선언식을 JIT 컴파일러로 미리 컴파일하여 런타임 중에 빠르게 실행할 수 있도록 설계되었다. 따라서 함수 선언식은 매우 빈번하게 호출되는 상황에서 성능상의 미세한 차이가 나타날 수 있다.


💡 구체적인 성능 차이 발생 예시

복잡한 애플리케이션에서, 함수 호출이 빈번하거나 동적으로 생성된 함수를 사용하는 경우에 함수 선언식이 함수 표현식보다 성능적으로 조금 더 유리할 수 있다. 예를 들어, 반복적인 작업을 처리하는 함수가 전역 스코프에서 선언되어 여러 모듈이나 함수에서 빈번하게 호출되는 경우, 함수 선언식은 호출 시마다 함수 선언이 메모리에서 빠르게 참조되므로 성능적으로 이점을 가질 수 있다.

function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

console.log(factorial(10));  // 3628800

위 코드에서 factorial 함수는 재귀적으로 호출되며, 이 함수는 미리 메모리에 할당되어 있기 때문에 반복적으로 호출할 때 추가적인 메모리 할당이 발생하지 않는다.


💡 성능 차이가 거의 없는 이유

다만, 이러한 성능 차이는 단일 함수 호출이나 간단한 애플리케이션에서는 거의 느껴지지 않는다. 함수가 메모리에 미리 할당되었다고 하더라도, 자바스크립트의 JIT 컴파일러는 함수 표현식에서도 상당히 빠른 최적화를 수행하기 때문이다. 이는 V8 엔진과 같은 최신 자바스크립트 엔진이 동적 메모리 할당을 매우 빠르게 처리할 수 있기 때문이다.

결론적으로 함수 선언식과 표현식 사이의 성능 차이는 미세하며, 단순한 코드나 대규모 프로젝트에서도 체감할 수 없는 경우가 많다. 성능 최적화는 주로 함수 선언식이나 표현식의 사용보다는 메모리 관리, 콜백 함수 처리 같은 더 복잡한 측면에서 발생한다.


📌 사용 시기 및 사용 사례

함수 선언식은 특히 전역적으로 접근 가능한 함수를 정의할 때 유용하다. 여러 군데에서 재사용해야 하는 유틸리티 함수나 반복적으로 호출할 필요가 있는 함수 정의에 적합하다. 아래와 같이 자주 호출되는 함수는 함수 선언식을 사용하는 것이 좋다.

function calculateSum(a, b) {
  return a + b;
}

console.log(calculateSum(5, 10));  // 15

📌 주의사항

  • 함수 선언식은 전역 스코프에서 사용될 경우, 같은 이름을 가진 함수나 변수와 충돌할 수 있으므로, 지역 스코프나 모듈화된 구조에서 사용하는 것이 좋다.
  • 중복 함수 정의 시 주의해야 한다. 자바스크립트는 후에 선언된 함수로 덮어쓰기를 수행하므로, 의도치 않은 함수 재정의가 발생할 수 있다.

함수 표현식 (Function Expression)

📌 정의 및 문법

함수 표현식은 함수를 변수에 할당하는 방식으로, 런타임에 함수를 정의하고 필요한 시점에 호출하는 방식이다. 함수 선언식과는 달리 호이스팅되지 않기 때문에 함수가 정의된 이후에만 호출할 수 있다는 중요한 차이점이 있다. 함수 표현식은 자주 익명 함수와 함께 사용되며, 변수에 할당된 함수는 해당 변수를 통해 호출된다.

const 변수명 = function(매개변수1, 매개변수2, ...) {
  // 실행할 코드
  return 반환값;
};

또한 함수 표현식은 기명 함수로도 정의할 수 있지만, 주로 익명 함수가 더 일반적이다. 익명 함수는 이름이 없기 때문에 오로지 변수명을 통해 호출되며, 이를 통해 유연하게 사용 가능하다.

const multiply = function(a, b) {
  return a * b;
};

console.log(multiply(2, 3));  // 6

위 코드에서는 multiply라는 변수에 익명 함수가 할당되어 있으며, 이 함수는 변수명을 통해 호출될 수 있다.


💡 익명 함수 예시

익명 함수는 함수 표현식에서 자주 사용되며, 특히 콜백 함수로 사용될 때 유용하다.

const sayHello = function() {
  return 'Hello, world!';
};

console.log(sayHello());  // "Hello, world!"

💡 기명 함수 예시

기명 함수는 재귀 함수와 같은 상황에서 유용하게 사용된다.

const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1);
};

console.log(factorial(5));  // 120

위 코드에서 fact는 함수 이름으로, 외부에서 호출되지는 않지만 함수 내부에서 재귀 호출 시 사용된다.


📌 함수 표현식의 중요한 특징

  1. 호이스팅이 발생하지 않음: 함수 표현식은 선언문과 달리 호이스팅이 발생하지 않는다. 이는 함수가 선언되기 전에 호출할 수 없다는 의미이다. 이 때문에 코드 구조가 보다 명확하고 논리적 순서를 따르게 된다.
  2. 익명 함수와 기명 함수: 함수 표현식에서 주로 익명 함수를 사용하지만, 기명 함수도 사용할 수 있다. 익명 함수는 함수 이름이 없고 변수에 할당된 함수명으로 호출되며, 기명 함수는 함수 내부에서 재귀 호출 등을 위해 이름을 사용할 수 있다.
  3. 동적 할당: 함수 표현식은 특정 조건에 따라 동적으로 함수를 할당할 수 있다. 예를 들어 조건문 내부에서 함수를 정의하거나, 특정 시점에서만 필요한 함수로 활용할 수 있다.
  4. 런타임 평가: 함수 표현식은 런타임에 평가되어 메모리에 할당된다. 즉, 코드 실행 중에 함수가 메모리에 올라가며, 필요한 시점에만 할당되어 메모리 사용을 최적화할 수 있다.

💡 또한, 함수 표현식에서 자주 사용되는 화살표 함수(Arrow Function)는 this 바인딩이 일반 함수와 다르게 동작하는 등 여러 유용한 특징을 가지고 있다. 화살표 함수는 간결한 문법 덕분에 ES6 이후로 많이 사용되고 있는데, 이에 대해서는 다음 포스트에서 집중적으로 다룰 예정이다.


📌 함수 표현식의 성능과 메모리

💡 성능과 메모리 관리

함수 표현식은 호이스팅되지 않기 때문에, 함수가 정의되기 전에 호출할 수 없다. 이로 인해, 코드 작성 시 함수 선언의 위치와 호출 순서에 더욱 주의를 기울여야 한다. 함수 표현식은 메모리 관리 면에서도 런타임에 함수가 할당되므로, 런타임 중에 불필요한 함수 생성이 적다면 메모리 효율을 더 높일 수 있다.

즉, 이 방식은 코드가 실행되기 전까지 함수가 메모리에 올라가지 않으므로, 필요한 함수만 메모리에 할당되어 메모리를 보다 효율적으로 사용할 수 있다. 따라서 불필요한 메모리 점유를 줄일 수 있어, 선언문에 비해 메모리 효율이 더 좋을 수 있다.


💡 메모리 관리에서의 이점

함수 표현식은 런타임에 평가되기 때문에, 함수가 선언된 위치에서 메모리에 할당된다. 이 점은 함수 표현식이 호이스팅되지 않는다는 점과 연결된다. 런타임에 함수가 할당되므로, 메모리 상의 최적화가 더 세밀하게 이루어질 수 있다. 이를 통해 불필요한 메모리 할당을 피할 수 있다.

자바스크립트 엔진은 메모리를 할당할 때, 메모리 공간을 가능한 효율적으로 관리하려고 한다. 함수 표현식은 필요한 시점에서만 메모리에 할당되므로, 특정 코드 블록이 실행될 때만 메모리 공간을 차지한다. 이는 메모리 사용량을 줄이는 데 도움이 된다. 예를 들어, 특정 로직에서만 필요한 함수를 함수 표현식으로 정의하여 메모리 효율을 높일 수 있다.

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

console.log(calculateSum(10, 20)); // 30

위 코드는 calculateSum 함수가 const 변수에 할당된 후에만 메모리에 로드되며, 그 전에는 존재하지 않는다. 이는 메모리 사용을 최적화하는데 중요한 역할을 한다.


💡 성능상의 차이

함수 표현식의 성능 이점: 함수 표현식은 함수가 실행될 때만 메모리에 로드되기 때문에, 함수를 선언한 후 호출하지 않으면 불필요한 메모리 사용을 막을 수 있다. 이는 특히 동적 함수 정의에서 유용하다. 예를 들어, 조건문 내에서 특정 조건을 만족할 때만 함수를 정의하거나 호출할 수 있다.

let operation;
const mode = 'multiply';

if (mode === 'multiply') {
  operation = function(a, b) {
    return a * b;
  };
} else {
  operation = function(a, b) {
    return a + b;
  };
}

console.log(operation(5, 10));  // 50

위 코드는 조건에 따라 다른 함수를 동적으로 정의하여 사용한다. 이 경우 함수 표현식은 메모리 효율성을 높이고, 불필요한 함수 선언을 방지할 수 있다. 필요할 때만 함수가 정의되므로, 메모리 사용량을 줄이고 함수 실행 시 성능 최적화에도 기여한다.

V8 엔진을 비롯한 대부분의 자바스크립트 엔진은 함수 표현식도 매우 빠르게 최적화한다. 자바스크립트는 동적 언어이기 때문에 함수가 런타임에 정의되고 메모리에 할당되더라도, 최신 엔진에서는 함수 호출 시 최적화된 JIT 컴파일로 처리되어 성능 저하가 거의 발생하지 않는다.


💡 함수 표현식의 성능 문제

  • 콜백 함수로 전달 시 주의: 함수 표현식을 콜백으로 사용하는 경우, 특히 이벤트 처리나 비동기 작업에서 자주 사용되면, 함수가 필요 이상으로 계속 재정의될 수 있어 성능에 영향을 미칠 수 있다. 이 경우, 함수가 필요할 때마다 다시 생성되어 메모리에 할당되므로, 불필요한 메모리 사용을 초래할 수 있다. 즉, 매번 콜백 함수가 새롭게 생성되는 경우 메모리 누수가 발생할 수 있다.
setTimeout(function() {
  console.log("This is a callback function.");
}, 1000);

이 코드는 익명 함수가 반복적으로 호출될 경우 메모리 누수가 발생할 수 있다. 이를 방지하기 위해 함수 재사용을 고려해야 하며, 전역 또는 모듈 내에서 함수 선언을 통해 콜백 함수를 미리 정의하는 방식이 권장될 수 있다.


📌 클로저와의 관계

함수 표현식은 클로저와 밀접하게 연관되어 있다. 클로저는 함수가 선언된 환경을 기억하고, 그 환경의 변수에 접근할 수 있는 자바스크립트의 강력한 기능 중 하나이다. 클로저는 함수 표현식에서 외부 함수의 변수를 참조하는 경우 자주 사용된다.

function outerFunction(outerVariable) {
  return function innerFunction(innerVariable) {
    console.log(`Outer Variable: ${outerVariable}`);
    console.log(`Inner Variable: ${innerVariable}`);
  };
}

const newFunction = outerFunction('outside');
newFunction('inside');

📖 클로저에 대한 자세한 설명
[JavaScript] 클로저와 렉시컬 환경 (Closure, Lexical Environment)


함수 선언식 vs 함수 표현식

📌 호이스팅 차이

함수 선언식은 함수의 전체가 호이스팅되어 함수 선언 전에 호출할 수 있다. 반면, 함수 표현식은 변수가 호이스팅될 뿐, 함수 본문은 런타임에 할당되기 때문에 함수 선언 전에 호출하면 참조 오류가 발생한다.

console.log(greet('Alice'));  // "Hello, Alice!"
function greet(name) {
  return `Hello, ${name}!`;
}

console.log(multiply(2, 3));  // 오류 발생: multiply is not defined
const multiply = function(a, b) {
  return a * b;
};

📌 가독성 및 유지 보수성

  • 함수 선언식은 함수가 미리 정의되어 있어 코드 흐름을 직관적으로 이해할 수 있다. 이로 인해 대규모 프로젝트에서 가독성이 높아지는 경향이 있다.
  • 함수 표현식은 특정 시점에서만 호출되어야 하는 함수나, 특정 로직 내에서만 필요한 함수를 정의할 때 적합하다. 이로 인해, 유연한 코드 구조를 만들고자 할 때 유리하다.

📌 성능 비교

대부분의 경우 함수 선언식과 함수 표현식 간의 성능 차이는 미미하다. 다만, 함수 선언식은 런타임 이전에 메모리에 할당되므로 더 빠르게 호출할 수 있다. 반면 함수 표현식은 런타임에 함수가 할당되므로 성능에 미세한 영향을 미칠 수 있다. 하지만 이러한 차이는 실질적인 코드에서는 거의 무시할 수 있을 정도이다.

즉, 미세한 성능 차이가 있지만, 대부분의 경우 성능 차이가 체감되지 않는다.


📌 선택 기준

  1. 전역적 함수 정의: 유틸리티 함수나 여러 곳에서 반복적으로 호출될 함수는 함수 선언식이 적합하다.
  2. 한정된 범위 내에서 사용하는 함수: 특정 로직에서만 사용할 함수나 클로저를 활용할 때는 함수 표현식을 사용하는 것이 바람직하다.

📌 성능 최적화에서 중요한 점

함수 선언식과 함수 표현식 간의 성능 차이는 매우 미세하며, 자바스크립트 엔진은 대부분의 경우 성능을 최적화할 수 있다. 하지만, 함수 선언식은 빈번하게 호출될 함수를 미리 정의하고, 함수 표현식은 특정 시점에만 필요할 함수를 동적으로 정의할 때 사용해야 메모리 효율성을 극대화할 수 있다.

또한 성능 최적화에서 중요한 점은, 함수 자체의 선언 방식보다는 비동기 처리, 클로저, 재귀 같은 함수 활용 방식에 따라 성능과 메모리 관리가 달라진다는 것이다. 성능을 향상시키기 위해서는 불필요한 함수 호출을 줄이고, 필요할 때만 메모리를 할당하는 방식이 바람직하다.


참고문헌

4개의 댓글

comment-user-thumbnail
2024년 9월 16일

마침 함수 정의 방법에 대해 공부하고 있었는데 치현님 글이 맞춰 업데이트 됐네여 ㅋ...
js v8 엔진의 JIT 컴파일로 인해서 함수 표현식과 함수 선언문에 큰 차이가 없없군요
유틸 함수처럼 자주 사용되는 함수는 함수 선언문, 로직내에서 한번만 실행되는데 애들은 함수 표현식
모두 전역 실행컨텍스트 생성 과정에서 선언부 등록에 연관이 있군요 쌩유👍👍👍👍👍

1개의 답글
comment-user-thumbnail
2024년 9월 23일

오 어떤 차이가 있는지만 알고있었는데..! 차이점에 따라 어떻게 활용하면 되는지 잘 배우고 갑니다~~~👍

1개의 답글