함수 리터럴
상수 (Constant)
: 변하지 않는 변수 (참조 변수 - 주소 값이 변하지 않는 것이다.)
리터럴 (Literal)
: 변하지 않는 데이터 그 자체를 의미함.
const a = 1;
// a -> 상수 // 1 -> 리터럴
리터럴
은 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기 방식
을 말한다.
- 즉, 리터럴은 값을 생성하기 위한 표기법이다. 따라서 함수 리터럴도 평가되어 값을 생성하며, 이 값은 객체다. 즉, 함수는 객체다. (값의 성질을 갖는
일급 객체
에 해당한다.)
함수 정의
- 함수 정의 : 함수 호출 전, 인수를 전달 받을 매개변수와 실행할 문들, 그리고 리턴 값을 지정하는 것.
- 변수는 선언, 함수는 정의라고 하는데 이는 메모리 할당 여부에 따른 표현의 차이이다.
- 선언 : 메모리 할당이 되지 않지만 이름은 있음. 정의 : 메모리도 할당되고 이름도 있는 상태.
함수 선언문
function add(x, y) {
return x + y;
}
함수 선언문
은 함수 리터럴
과 동일한 형태. 그러나 함수 선언문
은 이름을 생략할 수 없다.
함수 선언문
은 ‘표현식이 아닌 문’
이다. (표현식인 문
은 값으로 평가되어 변수에 할당할 수 있으나, 표현식이 아닌 문
은 값으로 평가되지 않기 때문에 할당할 수 없다. )
let add = function add(x, y) {
return x + y;
} // 하지만 이 예시를 보면 add라는 변수에 할당되는 것 처럼 보인다.
- 이러한 이유는 JS 엔진이 코드의 문맥에 따라 동일한
함수 리터럴
을 표현식이 아닌 문
과 표현식인 문
으로 해석하는 경우가 있기 때문이다. 함수 선언문
은 이름을 제외할 수 없다는 점을 제외하면 함수 리터럴
과 형태가 동일하기 때문에, 중의적으로 해석될 가능성이 있다.
- 함수 이름이 있는 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석하고, 함수 리터럴이 값으로 평가되어야 하는(함수 리터럴이 피연산자로 사용되었을 경우) 문맥에서는 함수 리터럴 표현식으로 해석한다. (어쨌든 함수가 생성되는 것은 동일하다.)
function foo() {console.log('foo');});
foo(); //foo
(function bar() {console.log('bar');});
bar(); //ReferenceError: bar is not defined.
그룹 연산자 ()
내에 있는 함수 리터럴(bar)
는 함수 리터럴 표현식
으로 해석되었다. 그룹 연산자의 피연산자는 값으로 평가될 수 있는 표현식이어야 하기 때문이다. 그리고 이렇게 생성된 bar 함수는 호출할 수 없다.
함수 리터럴
에서 함수의 이름은 함수 몸체 내에서만 참조할 수 있는 식별자이다. 따라서 외부에서는 bar라는 이름을 참조할 수 없기 때문에 이름으로는 함수를 호출할 수 없다는 것이다. (함수를 가리키는 식별자가 없다는 것과 마찬가지.)
- 하지만
함수 선언문
으로 정의된 함수는 foo
라는 이름으로 호출할 수 있다. foo
라는 함수 이름은 함수 몸체 내에서만 유효한 식별자이기 때문에 외부에서 호출을 할 수 없어야 한다. 이렇게 호출이 가능한 이유는 JS 엔진이 암묵적으로 foo
라는 식별자를 생성했기 때문이다.
- JS엔진은 함수 선언문을 해석하여 함수 객체를 생성하는데, 이 때 함수 리터럴의 이름은 함수 내부에서만 유효하기 때문에 별도로 생성된 함수 객체를 가리키는 식별자가 필요하다. 따라서 함수 이름가 동일한 함수 객체의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당하는 것이다.
- 함수는 함수 이름으로 호출하는 것이 아니라, 함수 객체를 가리키는 식별자로 호출한다. 함수 선언문에서 함수를 호출할 수 있는 식별자는, 함수 내부의 식별자가 아니라, 그것과 동일하게 JS 엔진이 암묵적으로 생성한 객체의 식별자인 것.
함수 표현식
- 함수는 값의 성질을 갖는 일급 객체이기 때문에, 이를 변수에 할당할 수 있다. 이러한 정의 방식을 함수 표현식 이라고 한다.
- 함수 표현식에서는 일반적으로 함수 이름을 제거한 익명 함수를 사용한다. 이 때 함수를 호출하는 식별자는 함수 선언문 방식과 마찬가지로 함수 객체를 가리키는 식별자이다.
함수 생성 시점과 함수 호이스팅
- 함수 선언문으로 정의한 함수는 호이스팅된다.
- 함수 표현식으로 정의한 함수는 호이스팅 되지 않는다.
- 이러한 차이가 발생하는 이유는 두 함수 정의 방식의 생성 시점이 다르기 때문이다.
- 함수 선언문으로 정의된 함수는 런타임 이전에 생성 단계(Creation Phase)에서 함수 객체가 먼저 생성되고, 암묵적으로 식별자를 생성해 함수 객체를 할당한다.
- 코드가 순차적으로 실행되는 런타임 단계에서 이미 함수 객체 생성 및 식별자 할당이 완료된 상태이므로, 함수 선언문 이전에 함수를 실행할 수 있는 호이스팅이 가능한 것이다.
- 함수 표현식의 경우 변수에 할당되는 값이 함수 리터럴인 문이다. 따라서 변수 선언 및 할당문과 동일하게 동작한다. 변수 선언의 경우 런타임 이전에
undefined
로 초기화되지만 (var
을 사용했을 경우. let
과 const
는 선언 시점 이전까지 초기화 되지 않는 TDZ
가 있다), 변수 할당문의 값은 실행 시점에 평가되기 때문에, 함수 리터럴도 실행 시점에 평가되어 함수 객체가 된다.
- 위와 같은 이유로 함수 표현식으로 함수를 정의할 경우, 함수 호이스팅이 아닌 변수 호이스팅이 발생한다.