let sayHi = function() {
alert("Hello");
};
함수를 생성하고 변수에 값을 할당하는 것처럼 함수가 변수에 할당되었습니다.
함수가 어떤 방식으로 만들어졌는지에 관계없이 함수는 값이고, 따라서 변수에 할당할 수 있습니다.
함수는 값이기 때문에 아래와 같이 alert를 이용하여 함수 코드를 출력할 수도 있습니다.
function sayHi() {
alert( "Hello" );
}
alert( sayHi ); // hello가 아닌 함수 코드가 보입니다.
마지막 줄에서 sayHi 옆에 괄호가 없기 때문에 함수는 실행되지 않습니다.
변수를 복사해 다른 변수에 할당하는 것처럼 함수도 다른 변수에 할당할 수도 있습니다.
function sayHi() { // (1)
alert( "Hello" );
}
let func = sayHi; // (2)
func(); // Hello (3)
sayHi(); // Hello
if {...}
, for {}
, function f { }
같이 중괄호로 만든 코드 블록 끝엔 ;이 없어도 됩니다.
함수 표현식은 let sayHi = ...;
와 같은 구문 안에서 값의 역할을 합니다.
코드 블록이 아니고 값처럼 취급되어 변수에 할당되죠.
그래서 모든 구문의 끝엔 세미 콜론을 붙이는 게 좋습니다.
함수 표현식에 쓰인 세미 콜론은 함수 표현식 때문에 붙여진 게 아니라, 구문의 끝이기 떄문에 붙여졌습니다.
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
function showOk() {
alert( "동의하셨습니다." );
}
function showCancel() {
alert( "취소 버튼을 누르셨습니다." );
}
// 사용법: 함수 showOk와 showCancel가 ask 함수의 인수로 전달됨
ask("동의하십니까?", showOk, showCancel);
함수 ask
의 인수, showOk
와 showCancel
은 콜백 함수 또는 콜백이라고 불립니다.
함수를 함수의 인수로 전달하고, 필요하다면 인수로 전달한 그 함수를 "나중에 호출(called back)"하는 것이 콜백 함수의 개념입니다.
위의 예시에서는 사용자가 "yes"라고 대답한 경우 showOk
가 콜백이 되고, "no"라고 대답한 경우 showCancel
가 콜백이 됩니다.
아래와 같이 함수 표현식을 사용하면 코드 길이가 짧아집니다.
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
ask(
"동의하십니까?",
function() { alert("동의하셨습니다."); },
function() { alert("취소 버튼을 누르셨습니다."); }
);
수 표현식과 선언문의 차이에 대해 알아봅시다.
첫 번째는 문법입니다. 코드를 통해 어떤 차이가 있는지 살펴봅시다.
// 함수 선언문
function sum(a, b) {
return a + b;
}
=
를 이용해 만든 "할당 표현식" 우측에 생성되었습니다. // 함수 표현식
let sum = function(a, b) {
return a + b;
};
두 번째 차이는 자바스크립트 엔진이 언제 함수를 생성하는지에 있습니다.
함수 표현식은 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성합니다. 따라서 실행 흐름이 함수에 도달했을 때부터 해당 함수를 사용할 수 있습니다.
위 예시를 이용해 설명해 보도록 하겠습니다. 스크립트가 실행되고, 실행 흐름이 let sum = function…
의 우측(함수 표현식)에 도달 했을때 함수가 생성됩니다. 이때 이후부터 해당 함수를 사용(할당, 호출 등)할 수 있습니다.
하지만 함수 선언문은 조금 다릅니다.
함수 선언문은 함수 선언문이 정의되기 전에도 호출할 수 있습니다.
따라서 전역 함수 선언문은 스크립트 어디에 있느냐에 상관없이 어디에서든 사용할 수 있습니다.
이게 가능한 이유는 자바스크립트의 내부 알고리즘 때문입니다. 자바스크립트는 스크립트를 실행하기 전, 준비단계에서 전역에 선언된 함수 선언문을 찾고, 해당 함수를 생성합니다. 스크립트가 진짜 실행되기 전 "초기화 단계"에서 함수 선언 방식으로 정의한 함수가 생성되는 것이죠.
스크립트는 함수 선언문이 모두 처리된 이후에서야 실행됩니다. 따라서 스크립트 어디서든 함수 선언문으로 선언한 함수에 접근할 수 있는 것입니다.
예시를 살펴봅시다.
*!*
sayHi("John"); // Hello, John
*/!*
function sayHi(name) {
alert( `Hello, ${name}` );
}
함수 선언문, sayHi
는 스크립트 실행 준비 단계에서 생성되기 때문에, 스크립트 내 어디에서든 접근할 수 있습니다.
그러나 함수 표현식으로 정의한 함수는 함수가 선언되기 전에 접근하는 게 불가능합니다.
*!*
sayHi("John"); // error!
*/!*
let sayHi = function(name) { // (*) 마술은 일어나지 않습니다.
alert( `Hello, ${name}` );
};
함수 표현식은 실행 흐름이 표현식에 다다랐을 때 만들어집니다. 위 예시에선 (*)
로 표시한 줄에 실행 흐름이 도달했을 때 함수가 만들어집니다. 아주 늦죠.
세 번째 차이점은, 스코프입니다.
엄격 모드에서 함수 선언문이 코드 블록 내에 위치하면 해당 함수는 블록 내 어디서든 접근할 수 있습니다. 하지만 블록 밖에서는 함수에 접근하지 못합니다.
예시를 들어 설명해 보겠습니다. 런타임에 그 값을 알 수 있는 변수 age
가 있고, 이 변수의 값에 따라 함수 welcome()
을 다르게 정의해야 하는 상황입니다. 그리고 함수 welcome()
은 나중에 사용해야 하는 상황이라고 가정해 보죠.
함수 선언문을 사용하면 의도한 대로 코드가 동작하지 않습니다.
let age = prompt("나이를 알려주세요.", 18);
// 조건에 따라 함수를 선언함
if (age < 18) {
function welcome() {
alert("안녕!");
}
} else {
function welcome() {
alert("안녕하세요!");
}
}
// 함수를 나중에 호출합니다.
*!*
welcome(); // Error: welcome is not defined
*/!*
함수 선언문은 함수가 선언된 코드 블록 안에서만 유효하기 때문에 이런 에러가 발생합니다.
또 다른 예시를 살펴봅시다.
let age = 16; // 16을 저장했다 가정합시다.
if (age < 18) {
*!*
welcome(); // \ (실행)
*/!*
// |
function welcome() { // |
alert("안녕!"); // | 함수 선언문은 함수가 선언된 블록 내
} // | 어디에서든 유효합니다
// |
*!*
welcome(); // / (실행)
*/!*
} else {
function welcome() {
alert("안녕하세요!");
}
}
// 여기는 중괄호 밖이기 때문에
// 중괄호 안에서 선언한 함수 선언문은 호출할 수 없습니다.
*!*
welcome(); // Error: welcome is not defined
*/!*
그럼 if
문 밖에서 welcome
함수를 호출할 방법은 없는 걸까요?
함수 표현식을 사용하면 가능합니다. if
문 밖에 선언한 변수 welcome
에 함수 표현식으로 만든 함수를 할당하면 되죠.
이제 코드가 의도한 대로 동작합니다.
let age = prompt("나이를 알려주세요.", 18);
let welcome;
if (age < 18) {
welcome = function() {
alert("안녕!");
};
} else {
welcome = function() {
alert("안녕하세요!");
};
}
*!*
welcome(); // 제대로 동작합니다.
*/!*
물음표 연산자 ?
를 사용하면 위 코드를 좀 더 단순화할 수 있습니다.
let age = prompt("나이를 알려주세요.", 18);
let welcome = (age < 18) ?
function() { alert("안녕!"); } :
function() { alert("안녕하세요!"); };
*!*
welcome(); // 제대로 동작합니다.
*/!*
제 경험에 따르면 함수 선언문을 이용해 함수를 선언하는 걸 먼저 고려하는 게 좋습니다. 함수 선언문으로 함수를 정의하면, 함수가 선언되기 전에 호출할 수 있어서 코드 구성을 좀 더 자유롭게 할 수 있습니다.
함수 선언문을 사용하면 가독성도 좋아집니다. 코드에서 `let f = function(…) {…}`보다 `function f(…) {…}` 을 찾는 게 더 쉽죠. 함수 선언 방식이 더 "눈길을 사로잡습니다".
그러나 어떤 이유로 함수 선언 방식이 적합하지 않거나, (위 예제와 같이) 조건에 따라 함수를 선언해야 한다면 함수 표현식을 사용해야 합니다.