이야기에 들어가기 앞서 함수 선언식과 함수 표현식의 표기 방법의 차이만 가볍게 적고 들어가보자
// 함수 선언식
function logHi(){
console.log('hi')
}
// 함수 표현식
const logHi = function(){
console.log('hi')
}
우선 이정도만 머리 속에 집어넣고 하나씩 차례대로 알아가보자
자바스크립트를 처음 공부 할 때 가장 먼저 배웠던 내용 같은데 막상 답하려고 하니 까먹어서 이것 저것 뒤져 공부해봤다.
우선 프로그래밍 언어를 이루는 모든 문들은 선언식과 표현식의 조합으로 이뤄져있다고 볼 수 있다.
선언식이란 프로그램의 실행 흐름을 제어하거나 변수를 선언하는 코드의 조각을 의미 한다.
즉 if , for , while
과 같은 제어식부터 var , let , const
와 같은 변수 선언과 관련된 코드 조각을 선언식이라고 한다.
표현식이란 어떤 값으로 이행하는 임의의 유효한 코드 단위를 말한다.[1] 조금 더 쉽게 풀어 말하면 실행 결과가 값으로 평가 가능한 코드 조각이라 표현 할 수 있을 것이다.
값을 반환하는 코드 조각들은 다양한 연산자부터 하여 자바스크립트의 원시값인 리터럴이나 객체를 호출하는 행위, 변수를 할당하는 행위 모두 값을 반환하니 표현식이라 할 수 있다.
MDN 에선 다음과 같은 예시들을 모두 표현식이라 정의한다.
산수: 숫자, 예컨대 3.14159로 평가됩니다. 보통 산술 연산자를 사용합니다.
문자열: 문자열, 예컨대 "프레디", "234" 등으로 평가됩니다. 보통 문자열 연산자를 사용합니다.
논리: 참이나 거짓으로 평가됩니다. 대개 논리 연산자를 포함합니다.
일차 표현식: JavaScript의 키워드와 일반 표현식입니다.
좌변 표현식: 좌변 값은 할당의 목적지입니다.
그럼 다시 위에서 말한 예시를 가져와서 의미를 살펴보자
// 함수 선언식
function logHi(){
console.log('hi')
}
// 함수 표현식
const logHi = function(){
console.log('hi')
}
두 방식의 차이는 함수를 사용 할 때 function(){...}
을 선언식으로 사용하였는지, 표현식으로 사용하였느지를 나타낸다.
첫 번째 예시에서 사용 된 function (){...}
은 좌변에 할당되는 목적지가 없어 선언문으로 사용 되었다.
두 번째 예시에서 사용 된 function(){...}
은 좌변에 할당 될 목적지가 존재하여 표현식으로 사용 되었다.
두 개의 표면적인 차이로는 함수 생성을 좌변 표현식을 통해 특정 변수에 할당하였는가, 할당하지 않고 선언하였는가로 볼 수 있다.
함수의 정의는 자기 자신을 포함하여 정의 된 컨텍스트 내부에 존재하는 모두가 호출가능한 하위 프로그램을 의미 한다. [2]
호출 가능하기 위해서는 해당 프로그램을 식별 할 수 있는 식별자가 필요한데, 함수 선언식은 특정 변수에 할당하지 않기 때문에
함수를 식별 할 수 있는 이름이 필요하다.
이에 함수 선언식은 유명 함수 형태로 선언해야만 한다.
만약 함수 선언식을 무명 함수 형태로 선언하게 되면 SyntaxError 를 마주 하게 될 것이다.
함수 선언식은 var, let, const
와 같은 선언문이기 때문에 실행 전 호이스팅 된다.
다만 변수 선언문과 다르게 호출 가능한 형태 로 호이스팅 된다는 점이 차이점이다.
// 변수의 호이스팅
console.log(a) // undefined
var a = 1
console.log(a) // 1
// 함수의 호이스팅
logHi() // 'hi'
function logHi(){
console.log('hi')
}
이는 함수 선언문 자체의 자체적인 특징으로 변수 호이스팅의 특징을 먼저 리캡 할 필요가 있다.
변수의 호이스팅은 실행 전 인터프리팅 단계에서 변수 표현식이 평가되어 할당 되기 전 메모리에 평가 되기 전 변수의 값을 담아두는 것을 의미 한다.
하지만 함수 선언식은 할당 될 변수가 존재하지 않는다. 이에 함수 선언식은 인터프리팅 단계에서 메모리에 모든 값이 올라가 호출 가능한 형태로 호이스팅 된다.
// 함수 표현식
const logHi = function(){
console.log('hi')
}
이렇게 함수를 값으로 평가하여 어떤 값에 할당하거나 인수로 건내 줄 수 있는 함수를 일급 객체 라고 이야기 한다.
즉 함수 표현식은 일급 객체인 함수를 반환한다 할 수 있다.
이러한 특징으로 즉시 실행 함수가 가능한 이유도 함수 표현식이 반환하는 값이 함수이기 때문이다.
(function () {
console.log("hi");
})();
즉시 실행 함수에서 첫 번째 ()
내부에 존재하는 함수 표현식은 함수 자체를 반환하고 반환된 함수를 호출한 것이기 때문이다.
콜백 함수 또한 함수 표현식이 반환하는 값이 함수이기 때문에 가능하다.
[1,2,3].forEach(function(item){console.log(item);});
forEach
함수에게 인자로 건내주는 함수는 function(item){...}
로 표현된 함수 표현식의 평과 결과인 함수이기때문이다.
함수 표현식은 함수를 어떻게 생성하는지 상관 없이 평가 후의 값만 중요하기 때문에
함수를 생성 할 때 이름을 지정할 필요가 없다. 만약 지정하였다 하더라도 생성 시 사용한 함수명은 참조 되지 않는다.
const logHi = function awesomeFunctionName() {
console.log("hi");
};
logHi();
awesomeFunctionName(); // ReferenceError: awesomeFunctionName is not defined
위에서 말한 내용을 토대로 알아볼 수 있는 것은 함수 표현식은 값으로 평가되는 표현식이라는 것이다.
즉 함수 표현식 자체는 호이스팅이 불가능 하다. 사실 이는 당연한 말로 함수 표현식 자체는 1 + 1
과 같은 코드와 동일하기 때문이다.
// 두 개 모두 표현식의 일부
1 + 1;
(function () {});
함수 표현식은 그저 단순히 값으로 평가 가능한 표현식의 일부이다.
그렇기에 함수 표현식의 호이스팅은 변수에 할당 할 때 호이스팅 되며, 이 때의 호이스팅 로직은 변수의 호이스팅 로직을 따른다.
console.log(a);
console.log(b); // ReferenceError: Cannot access 'b' before initialization
var a = function () {};
const b = function () {};