2025. 5. 20. 09:00ㆍJavascript/Javascript

자바스크립트는 함수를 특별한 종류의 값으로 취급한다. 여느 다른 언어들에서처럼 '어떠한 동작을 하는 구조' 정도로 취급하지 않는다는 얘기다.
스크립트를 작성하다 보면 유사한 동작을 하는 코드가 여러 곳에서 필요할 때가 많다. 사용자가 로그인이나 로그아웃을 했을 때 안내 메시지를 보여주는 동작 같은 경우 등등. 함수는 프로그램을 구성하는 주요 구성 요소이다. 함수를 이용하면 중복 없이 유사한 동작을 하는 코드를 여러 번 호출할 수 있다.
함수 선언문(Function Declarations)
일반적인 프로그래밍 언어에서의 함수 선언과 비슷한 형식이다. 함수명이 정의되어 있고 별도의 할당 명령이 없는 것. 함수 선언식은 자바스크립트에서 가장 기본적인 함수 정의 방식으로 함수 이름을 명시적으로 제공해야 한다.
function 함수명() {
구현 로직
}
// 예시
function funcDeclarations() {
return 'A function declaration';
}
funcDeclarations(); // 'A function declaration'
이 방식의 중요한 특징은 호이스팅이 발생한다는 점이다. 즉 함수 선언부가 실제로 호출되는 위치보다 위로 끌어올려져서 함수 선언 전에 호출할 수 있다는 것이다.
함수 선언문의 장점
1. 호이스팅으로 인한 유연성
: 함수 선언식은 코드 상단으로 끌어올려지기 때문에 함수 선언 위치에 상관없이 호출할 수 있다.
2. 디버깅에 용이
: 명시적인 함수 이름을 사용하기 때문에 디버깅을 할 때 오류 메시지나 콜스택에서 함수 이름이 명확하게 표시된다.
3. 코드의 가독성
: 코드 흐름상 함수가 미리 선언된 상태에서 호출되기 때문에 코드의 읽기 흐름이 자연스럽고 이해하기 쉽다.
4. 전역/지역 스코프 처리에 용이
: 함수 선언식은 코드 블록 안에 위치하더라도 전역 또는 지역 스코프에 맞춰 쉽게 관리된다.
5. 모듈화와 재사용
: 동일한 함수 이름으로 여러 군데에서 호출이 가능하며 재사용성이 높다.
함수 선언문의 동작 원리: 호이스팅(Hoisting)
자바스크립트의 함수 선언문은 변수 선언과 달리 전체 함수의 본문이 코드 상단으로 끌어올려진다. 이를 호이스팅이라 하며 다음과 같은 코드에서 볼 수 있다.
console.log(greet('monkey')) // "Hello, monkey!"
function greet(name) {
return `Hello, ${name}!`
}
위의 코드는 함수가 선언되기 전에 호출되었음에도 불구하고 정상적으로 실행된다. 이는 함수 선언식이 메모리에 먼저 할당되기 때문이다.
호이스팅에 대한 자세한 내용은 아래 글 참조
[Javascript] 호이스팅(Hoisting)의 발생 원리
자바스크립트에서 호이스팅(Hoisting)이란 변수 또는 함수의 선언이 맨 위로 끌어올려지는 현상을 말한다. 그래서 개발자가 어느 라인 어느 위치에 코드를 선언하더라도 실행되기 전 해당 코드가
developing-move.tistory.com
함수 선언문은 특히 전역적으로 접근 가능한 함수를 정의할 때 유용하다. 여러 군데에서 재사용해야 하는 유틸리티 함수나 반복적으로 호출할 필요가 있는 함수 정의에 적합하다. 아래와 같이 자주 호출되는 함수는 함수 선언문을 사용하는 것이 좋다.
함수 선언문 사용 시 주의사항
함수 선언문은 전역 스코프에서 사용될 경우 같은 이름을 가진 함수나 변수와 충돌할 수 있으므로 지역 스코프나 모듈화된 구조에서 사용하는 것이 좋다.
또한 중복 함수 정의 시에도 주의해야 한다. 자바스크립트는 후에 선언된 함수로 덮어쓰기를 수행하므로 의도치 않은 함수 재정의가 발생할 수 있다.
함수 표현식(Function Expressions)
유연한 자바스크립트 언어의 특징을 활용한 선언 방식이다. 정의한 function을 별도의 변수에 할당시키는 것.
const 함수명 = function () {
구현 로직
};
// 예시
const funcExpression = function () {
return 'A function expression';
}
funcExpression(); // 'A function expression'
함수 표현식이란 변수에 값을 할당시키는 것처럼 먼저 함수를 생성하고 그 함수를 변수에 할당시키는 것이다. 함수가 어떠한 방식으로 만들어졌는지와는 관계 없이 함수는 값이므로 변수에 할당시킬 수 있는 것이다. 위 예시에서는 함수가 변수 funcExpression에 할당되었다.
결국 함수 표현식은 함수를 변수에 할당하는 방식으로, 런타임에 함수를 정의하고 필요한 시점에 호출하는 방식이다. 함수 선언식과는 달리 호이스팅이 일어나지 않기 때문에 함수가 정의된 이후에만 호출할 수 있다는 중요한 차이점이 있다. 함수 표현식은 자주 익명 함수와 함께 사용되며 변수에 할당된 함수는 해당 변수를 통해 호출된다.
물론 함수 표현식 또한 기명 함수로도 정의할 수도 있지만 주로 익명 함수로 쓰는 것이 일반적이다. 익명 함수는 이름이 없기 때문에 오로지 변수명을 통해 호출되며, 이를 통해 유연하게 사용이 가능하다. 함수 표현식에서 익명 함수는 특히 콜백 함수로 사용될 때 유용하며, 기명 함수는 재귀 함수와 같은 상황에서 유용하게 사용된다.
// 함수 표현식: 익명 함수 사용 예시
const sayHello = function() {
alert('Hello World!')
}
document.querySelector('button').addEventListener('click', sayHello)
// 함수 표현식: 기명 함수 사용 예시
const factorial = function fact(n) {
if ( n<=1 ) return 1;
return n * fact( n - 1 )
};
위 코드에서 fact는 함수 이름으로 외부에서 호출되지는 않지만 함수 내부에서 재귀 호출 시 사용된다.
함수 표현식의 특징
1. 호이스팅이 발생하지 않음
: 함수 표현식은 선언문과 달리 호이스팅이 발생하지 않는다. 이는 함수가 선언되기 전에 호출할 수 없다는 의미이다. 이 때문에 코드 구조가 보다 명확하고 논리적인 순서를 따르게 된다.
2. 익명 함수와 기명 함수
: 함수 표현식에서 주로 익명 함수를 사용하지만, 기명 함수도 사용할 수 있다. 익명 함수는 함수 이름이 없고 변수에 할당된 함수명으로 호출되며, 기명 함수는 함수 내부에서 재귀 호출 등을 위해 이름을 사용할 수 있다.
3. 동적 할당
: 함수 표현식은 특정 조건에 따라 동적으로 함수를 할당할 수 있다. 예를 들어 조건문 내부에서 함수를 정의하거나 특정 시점에만 필요한 함수로 활용할 수 있다.
4. 런타임 평가
: 함수 표현식은 런타임에 평가되어 메모리에 할당된다. 즉 코드 실행 중에 함수가 메모리에 올라가며 필요한 시점에만 할당되므로 메모리 사용을 최적화할 수 있다.
함수 표현식 vs 함수 선언문
함수 표현식과 함수 선언문의 차이에 대해 알아보자.
주요 차이는 자바스크립트 엔진이 언제 함수를 생성하는지, 즉 호이스팅에 있다.
함수 표현식은 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성한다. 따라서 실행 흐름이 함수에 도달했을 때부터 해당 함수를 사용할 수 있게 된다.
// 함수 표현식
var sum = function(a, b) { // *
return a + b;
};
위 예시를 통해 설명해 보자면 스크립트가 실행되고 실행 흐름이 let sum = function...의 우측(함수 표현식)에 도달했을 때 함수가 생성된다. 위 예시에서는 *로 표시한 줄에 실행 흐름이 도달했을 때, 이때 이후부터 해당 함수를 사용(할당, 호출 등)할 수 있는 것이다.
반면 함수 선언문의 경우는 조금 다르다. 함수 선언문의 경우 함수 선언문이 정의되기 전에도 호출할 수 있다. 따라서 전역 함수 선언문은 스크립트 어디에 있느냐에 상관없이 어디에서든 사용할 수 있다.
이게 가능한 이유는 자바스크립트 내부 알고리즘 때문이다. 자바스크립트는 스크립트를 실행하기 전 준비 단계에서 전역에 선언된 함수 선언문을 찾고 해당 함수를 생성한다. 스크립트가 진짜 실행되기 전 초기화 단계에서 함수 선언 방식으로 정의한 함수가 생성되는 것이다.
sayHi("monkey"); // Hello, monkey
function sayHi(name) {
alert( `Hello, ${name}` );
}
위 예시에서 함수 선언문 sayHi는 스크립트 실행 준비 단계에서 생성되기 때문에 스크립트 내 어디에서든 접근할 수 있다. 그러나 함수 표현식으로 정의한 함수는 함수가 선언되기 전에 접근하는 게 불가능하다.
sayHi("monkey"); // Uncaught TypeError: sayHi is not a function
var sayHi = function(name) {
alert( `Hello, ${name}` );
};
함수 표현식의 장점
함수 표현식이 함수 선언문보다 유용하게 쓰이는 경우는 다음과 같다.
1) 클로저로 사용
2) 콜백으로 사용(다른 함수의 인자로 넘길 수 있음)
함수 표현식으로 클로저 생성하기
클로저는 함수를 실행하기 전에 해당 함수에 변수를 넘기고 싶을 때 사용된다. 이해를 돕기 위해 아래 예제를 살펴보자.
function tabsHandler(index) {
return function tabClickEvent(event) {
// 바깥 함수인 tabsHandler()의 index 인자를 여기서 접근할 수 있다.
console.log(index); // 탭을 클릭할 때마다 해당 탭의 index 값을 표시
};
}
var tabs = document.querySelectorAll('.tab');
var 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);
};
}
반복문의 실행이 끝난 후 사용자가 tab을 클릭했을 때 tabClickEvent()가 실행된다. 만약 클로저를 사용하지 않았다면 모든 tab의 index 값이 반복문의 마지막 값인 tabs.length와 같다.
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = tabsHandler(i);
}
그렇다면 이번에는 클로저를 사용하지 않은 예제를 보자.
var tabs = document.querySelectorAll('.tab');
var i;
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = function (event) {
console.log(i); // 어느 탭을 클릭해도 항상 tabs.length(i 의 최종 값)이 출력
};
}
위 소스는 탭이 3개라고 가정했을 때 어느 탭을 클릭해도 i는 for문의 최종 값인 3이 찍힌다. 문제점을 더 파악하기 쉽게 for문 안의 function()을 밖으로 꺼내서 선언해 보면,
var tabs = document.querySelectorAll('.tab');
var i;
var logIndex = function (event) {
console.log(i); // 3
};
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = logIndex;
}
longIndex가 실행되는 시점은 이미 for문의 실행이 모두 끝난 시점이다. 따라서 어느 탭을 눌러도 for문의 최종 값인 3이 찍히는 것이다.
이를 해결하기 위해 클로저를 사용하면,
function tabsHandler(index) {
return function tabClickEvent(event) {
// 바깥 함수인 tabsHandler 의 index 인자를 여기서 접근할 수 있다.
console.log(index); // 탭을 클릭할 때 마다 해당 탭의 index 값을 표시
};
}
var tabs = document.querySelectorAll('.tab');
var i;
for (i = 0; i < tabs.length; i += 1) {
tabs[i].onclick = tabsHandler(i);
}
for문이 수행될 때 각 i의 값을 tabsHandler()에 넘기고 클로저인 tabClickEvent()에서 tabsHandler()의 인자 값 index를 접근할 수 있게 된다. 따라서 우리가 원하는 각 탭의 index에 접근할 수 있다.
함수 표현식을 다른 함수의 인자 값으로 넘기기
함수 표현식은 일반적으로 임시 변수에 저장하여 사용한다.
// doSth 이라는 임시 변수를 사용
var doSth = function () {
// ...
};
함수 표현식을 임시 변수에 넣지 않고도 아래와 같이 콜백 함수로 사용할 수 있다.
$(document).ready(function () {
console.log('An anonymous function'); // 'An anonymous function'
});
jQuery를 사용할 때 많이 보던 문법으로 위아 아래의 코드 결과는 같다.
var logMessage = function () {
console.log('An anonymous function');
};
$(document).ready(logMessage); // 'An anonymous function'
자바스크립트 내장 API인 forEach()를 사용할 때도 콜백 함수를 사용할 수 있다.
var arr = ["a", "b", "c"];
arr.forEach(function () {
// ...
});
함수 표현식과 함수 선언문 중 어느 쪽이 더 나은 방식이냐에 대한 논의는 개발자들 사이에서도 의견이 분분하다. 함수 표현식이 선언문에 비해 가지는 장점은 많지만 결국에는 이러한 차이점을 인지한 상태에서 일관된 코드를 작성하는 게 중요하다는 생각이 든다.
'Javascript > Javascript' 카테고리의 다른 글
[Javascript] 화살표 함수(arrow function) (1) | 2025.05.22 |
---|---|
[Javascript] 함수의 기본 파라미터 (1) | 2025.05.21 |
[Javascript] ECMAScript 2015(ES6)+ (2) (3) | 2025.05.20 |
[Javascript] ECMAScript 2015(ES6)+ (1) (3) | 2025.05.19 |
[Javascript] && 연산자로 조건문 단축시키기 (1) | 2025.05.16 |