Sky Archive

JavaScript

[JavaScript] 함수 선언식과 함수 표현식, 차이와 장점

Anchovy ʕ-᷅ᴥ-᷄ʔ 2021. 12. 29. 11:13

안녕하세요🐱‍🐉

이번 글은 JavaScript에서 함수 선언식과 함수 표현식에 대해 알아보려고 합니다. 언뜻 보면 둘의 차이가 있을까 싶지만 차이도 함께 알아볼게요~!


함수 선언식 (Function Declarations)

일반적인 함수 선언 방식

function funcDeclarations() {
    return 'enchovy';
}

funcDeclarations();

 

함수 표현식 (Function Expressions)

자바스크립트 언어의 특징을 활용한 선언 방식

let funcExpression = function () {
    return 'enchovy';
}

funcExpression();

 

※ ES6에서 추가된 화살표 함수 방식을 정의할 수도 있다.

let funcArrow = (x,y) => (x+y)

함수 선언식과 표현식의 차이

스코프와 호이스팅

함수 선언문은 var와 같이 함수 스코프(function scope)를 가지고 let과 const 변수는 블록 스코프(block scope)를 갖는다. 또한, 함수 선언식은 코드가 실행되기 전에 로드되지만, 함수 표현식은 인터프리터가 해당 코드 줄에 도달할 때 로드된다. 함수 선언식은 호이스팅 되지만 함수 표현식은 호이스팅 되지 않으므로 정의된 범위에서 로컬 변수의 복사본을 유지할 수 있다.

// 선언 전에 호출되도 정상 동작된다
// 1
console.log(enchovy());
function enchovy() {
    return 1;
}

때문에 함수 선언식은 블록문 밖에서 호출이 가능하다. 

 // 로드되지 않아 error 발생
console.log(enchovy());
const enchovy = function() {
    return 1;
}

함수 표현식은 선언한 변수에 할당하는지에 따라 스코프가 달라진다.

 

함수 표현식의 장점

‘함수 표현식이 호이스팅에 영향을 받지 않는다’는 특징 이외에도 함수 선언식보다 유용하게 쓰이는 경우는 클로져 사용과 인자 값으로 전달 시가 있다.


함수 표현식으로 클로져 생성

클로져는 함수를 실행하기 전에 해당 함수에 변수를 넘기고 싶을 때 사용된다.

function tabsHandler(index) {
    return function tabClickEvent(event) {
        // 바깥 함수인 tabsHandler() 의 index 인자를 여기서 접근할 수 있다.
        console.log(index); // 탭을 클릭할 때 마다 해당 탭의 index 값을 표시
    };
}

let tabs = document.querySelectorAll('.tab');
let i;

for (i = 0; i < tabs.length; i += 1) {
    tabs[i].onclick = tabsHandler(i);
}

위 예제는 모든 .tab 요소에 클릭 이벤트를 추가하는 예제다. 주목할 점은 클로져를 이용해 tabClickEvent()에서 바깥 함수 tabsHandler()의 인자 값 index를 접근했다는 점이다.

function tabsHandler(index) {
    return function tabClickEvent(event) {
        console.log(index);
    };
}

for 반복문의 실행이 끝난 후, 사용자가 tab을 클릭했을 때 tabClickEvent()가 실행된다. 만약 클로져를 쓰지 않았다면 모든 tab의 index 값이 for 반복문의 마지막 값인 tabs.length 와 같다.

for (i = 0; i < tabs.length; i += 1) {
    tabs[i].onclick = tabsHandler(i);
}

 

- 클로져를 쓰지 않은 예제

let tabs = document.querySelectorAll('.tab');
let i;

for (i = 0; i < tabs.length; i += 1) {
    tabs[i].onclick = function (event) {
      console.log(i); // 어느 탭을 클릭해도 항상 tabs.length (i 의 최종 값) 이 출력
    };
}

탭이 3개라고 가정할 때, 어느 탭을 클릭해도 i는 for 반복문의 최종 값인 3이 찍힌다.

let tabs = document.querySelectorAll('.tab');
let i;
let logIndex = function (event) {
  console.log(i); // 3
};

for (i = 0; i < tabs.length; i += 1) {
    tabs[i].onclick = logIndex;
}

for 문 안의 함수를 밖에서 선언해보면 logIndex가 실행되는 시점은 이미 for 문의 실행이 모두 끝난 시점이기 때문에 어느 탭을 눌러도 for 문의 최종 값인 3이 출력된다.

- 이를 클로져를 적용한 예시

function tabsHandler(index) {
    return function tabClickEvent(event) {
        // 바깥 함수인 tabsHandler 의 index 인자를 여기서 접근할 수 있다.
        console.log(index); // 탭을 클릭할 때 마다 해당 탭의 index 값을 표시
    };
}

let tabs = document.querySelectorAll('.tab');
let i;

for (i = 0; i < tabs.length; i += 1) {
    tabs[i].onclick = tabsHandler(i);
}

for 반복문이 수행될 때 각 i 값을 tabsHandler()에 넘기고, 클로져인 tabClickEvent()에서 tabsHandler()의 인자 값 index를 접근할 수 있게 된다. 따라서, 우리가 원하는 각 탭의 index를 접근할 수 있다.


함수 표현식을 다른 함수의 인자 값으로 넘기기

함수 표현식은 일반적으로 임시 변수에 저장하여 사용한다.

// doSth 이라는 임시 변수를 사용
let doSth = function () {
  // ...
};

 

함수 표현식을 임시 변수에 넣지 않고도 아래와 같이 콜백 함수로 사용할 수 있다.

$(document).ready(function () {
  console.log('An anonymous function'); // 'An anonymous function'
});

 

jQuery를 사용할 때 많이 보던 방식

let logMessage = function () {
  console.log('An anonymous function');
};

$(document).ready(logMessage); // 'An anonymous function'

자바스크립트 내장 API 인 forEach()를 사용할 때도 콜백 함수를 사용할 수 있다.

 


결론

 

// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
};

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};

함수 선언식과 함수 표현식 모두 사용할 수는 있지만, Airbnb JavaScript Style Guide에서는 함수 선언식 대신 이름이 있는 기명 함수 표현식을 권장하고 있다.

 

 

 

 

 

 

 

 

 

ref. https://joshua1988.github.io/web-development/javascript/function-expressions-vs-declarations/