썸네일 출처: https://brunch.co.kr/@hongjyoun/116
Javascript에서 가장 중요한 개념 중 하나인 호이스팅에 대해 깊게 알아보자.
면접관의 꼬리질문에도 당황하지 않을 수 있도록!
면접관: 호이스팅에 대해 설명해 주시겠어요?
???: 선언문이 먼저 실행… var에서 발생하는…
면접관: 선언문이 ‘실행’ 된다고요? 그리고 let, const에서는 호이스팅이 발생하지 않는 건가요?
???: ….
💡 본 글에서는 호이스팅에 집중하기 위해
‘환경 레코드에 바인딩 된 Lexical Environment와 Variable Environment’
같은 실행컨텍스트와 밀접한 용어를 사용하지 않고, 간단히 ‘환경 레코드’로 표현하겠습니다.
호이스팅을 이해하기 위해선 변수 생성 단계에 대해 알아야 한다
클로저의 동작 원리를 설명할 때, JavaScript 엔진은 소스코드를 [평가, 실행] 2개의 단계로 나누어 처리한다고 했다
그렇지만 이를 변수 생성단계에 대입해서 본다면 좀 더 세분화된 3단계로 나뉘어진다.
아래 전역 스코프에 등록된 코드를 통해 예시를 들어보자.
코드 실행 이전에 실행 컨텍스트가 생성되고 초기화되는 단계이다. 이 과정에서 변수 선언, 함수 선언, 스코프 체인이 설정된다.
현재 컨텍스트에 존재하는 모든 ‘선언 부분들만’ 실행 컨텍스트의 환경 레코드의 ‘키’로 등록한다.
이렇게 환경 레코드에 등록된 선언부분은 해당 실행 컨텍스트의 스코프에서 참조하는 대상이 된다.
환경 레코드의 ‘키’로만 등록했으므로 ‘값’은 아직 할당되지 않은 상태여서 uninitialized 상태가 된다.
💡이처럼 JS에서는 모든 선언문이 런타임 이전 단계에서 먼저 실행된다는 것을 반드시 기억해야 한다. 이게 호이스팅의 가장 핵심적인 원리가 되기 때문이다.
JS 런타임으로, 코드가 한 줄씩 실행되는 단계이다. 이 단계에서 변수에 값이 할당되고 함수가 호출된다.
코드가 한 줄씩 실행되며 실제로 작성된 변수 선언문에 도착했을 때,
(선언 단계에서 등록했었던) 환경 레코드의 ‘키’로 등록한 변수들의 메모리를 확보하는 단계이다.
메모리를 확보해야 하기 위해 환경 레코드의 ’키’로 등록된 변수의 ‘값’으로 undefined를 할당한다.
undefined로 초기화된 변수에 실제 값을 할당한다.
💡 const 같이 코드 자체가 변수 선언과 할당이 동시에 이뤄져 있는 구조라면, 초기화 단계와 할당 단계가 거의 동시에 이뤄지는 것이다.
흔히들 알고있는 사전적인 지식은 다음과 같다.
JavaScript에서 호이스팅(hoisting)이란,
인터프리터가 코드를 실행하기 전에 함수, 변수, 클래스 또는 import문의선언문
을
해당 범위의 맨 위로 끌어올리는것처럼 보이는 현상
을 뜻한다.
만약 본인이 실행 컨텍스트의 꼬리질문에도 자신이 있다면 “실행 컨텍스트 생성 시 렉시컬 스코프 내의 선언이 끌어올려 지는 게 호이스팅 입니다” 라고 면접관 앞에서 대답하면 더 좋을 것이다.
좀 더 직관적인 이해를 위해, 호이스팅의 개념을 앞서 설명했던 변수 생성 단계 개념에 대입해 본다면 아래와 같이 설명할 수 있다.
"변수의
선언과, 초기화, 할당
단계에서선언만
코드의 최상단으로 끌어올려 지는 것처럼 보이는 현상" 인 것이다
즉, 앞서 말한 STEP 1 - 선언 단계(Declaration phase) 의 과정이 호이스팅인 것이다!
Javascript는 모든 선언문( var, let, const, function, class 키워드를 사용해서 선언하는 모든 식별자 )이 런타임 이전 단계에서 먼저 실행된다.
바로 이러한 특징 때문에 호이스팅이 발생하는 것이다.
가끔 호이스팅에 대한 블로그 글을 보면서 가장 많이 발생하는 오해가 “ let, const는 호이스팅이 발생하지 않는다 “ 라는 것이다.
그렇지만, 이는 틀린 말이다.
정확히 JS에서 모든 선언문은 런타임 이전에 선언 부분이 먼저 환경레코드의 ‘키’로 등록되므로
let, const 역시 호이스팅이 무조건 발생하는 것이다
호이스팅 관점에서 var와 let, const 의 차이점은
호이스팅은 var, let, const 모두 발생하는게 맞는 것이며 var의 차이점은 변수의 초기화 시점에 있다고 했다.
let, const로 선언된 변수는 STEP 1 - 선언 단계(Declaration phase) 와
STEP 2 - 초기화 단계(Initialization phase)가 분리되어 각각 진행됐었다.
그러나, var 키워드로 선언한 변수는 선언 단계와 초기화 단계가 한번에 이뤄지게 된다.
정확히 말하면 런타임 이전인 STEP 1 - 선언 단계(Declaration phase) 가 발생하는 시점에, STEP 2 - 초기화 단계(Initialization phase)가 같이 일어나는 것이다.
앞서 설명한 STEP 1 - 선언 단계(Declaration phase)의 예시에서 var를 추가로 생성하게되면 다음과 같다.
let, const는 환경 레코드의 ‘키’로만 등록되고 ‘값’으로 아무것도 등록되지 않기 때문에 uninitialized 상태가 되지만
var의 경우 초기화 단계까지 동시 발생하게 되므로 환경 레코드의 ‘키’로 등록됨과 동시에 ‘값’으로 undefined로 초기화까지 한번에 되는 것이다
그래서 많이들 봐왔듯이, var로 선언한 변수를 선언문 이전에 참조해도 에러가 발생하지 않고
undefined가 출력되는 현상이 발생하는 것이다
만약 변수를 선언했을 때 선언문 이전에 해당 변수를 참조하게 되면 어떤 일이 발생할까?
var와 let, const는 호이스팅이라는 공통점을 가지고 있지만
초기화 시점이 서로 다르다는 차이점으로 인해 여기서 서로 다른 동작을 하게 된다
여기서 등장하는 개념이 TDZ (Temporal Dead Zone)이다.
var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화하므로
해당 변수를 선언문 이전에 참조해도 undefined가 출력된다
그러나 let, const로 선언한 변수의 경우 호이스팅 시 환경 레코드의 ‘키’로만 등록할 뿐
초기화 과정은 발생하지 않는다고 했다.
그래서 변수의 초기화를 수행하기 전에 읽는 코드가 먼저 나타나면 ReferenceError가 발생한다.
이게 바로 TDZ (Temporal Dead Zone) 이다
let, const 키워드로 선언한 변수는 '선언 단계'와 '초기화 단계'가 분리되어 진행되므로
스코프의 시작 지점
부터 ~ 초기화 단계 시작 지점(변수 선언문)
까지 일시적 사각지대(TDZ) 에 빠지게 되는 것이다.
초기화 단계가 실행되기 이전에 변수에 접근하려고 하면 변수가 아직 초기화가 되지 않았기 때문에 참조 에러가 발생하는 것이다.
함수 역시 마찬가지로 호이스팅이 된다. 그러나 함수 호이스팅에 대해 알아보기 전에 먼저
‘함수 객체’에 대해 알아야 한다
함수 객체는 함수가 선언될 때 생성되는 특별한 객체이다.
이 객체는 함수의 동작과 관련된 속성들을 가지고 있으며, JS 런타임에서 함수가 호출될 때 실제로 함수 객체를 참조하여 실행하게 되는 것이다.
함수 객체는 일반 객체와 달리 실행 가능한 코드와 함수 스코프 정보를 포함하고 있기 때문에 함수로서의 역할을 수행하게 된다.
함수 객체는 함수와 관련된 모든 정보를 갖고 있다
name
— 함수명length
— 매개변수의 개수prototype
— 생성자의 프로토타입 객체[[Environment]]
— 함수 객체가 생성된 렉시컬 환경에 대한 참조- 그 외…
여기서는 호이스팅에 대한 내용만 다루고 있으므로 ‘함수 객체’는 함수를 실행하기 위해 필요한 정보들을 담고 있는 객체 라고 이해해도 된다.
그러나 실제로 ‘함수 객체’는 JS에서 가장 중요한 개념인 클로저의 동작 원리가 되므로 반드시 알아야 하는 개념이다.
함수 호이스팅은, 선언문과 표현식에 대한 차이점이 존재한다.
함수 선언문
var와 비슷하게, 함수는 호이스팅이 될 때 초기화 단계도 동시에 진행된다.
❗❗여기서 var는 undefined로 초기화가 됐지만, 선언문으로 생성된 함수는 '함수 객체'의 참조
값으로 초기화가 된다.
💡 함수 객체를 저장하는게 아닌 함수 객체의 ‘참조’를 저장한다고 했는데, 이유는 실제 함수 객체는 Heap메모리에 따로 존재하기 때문에 참조값을 바인딩하고 사용하는 것이다.
undefined가 아닌, 함수의 정보를 담고 있는 함수 객체의 참조값으로 초기화가 되므로
실제 선언문 이전에 호출해도 함수 호이스팅에 의해 함수로 호출이 가능한 것이다.
함수 표현식
호이스팅이 될 때 , 일반 변수와 동일한 방식으로 호이스팅이 된다.
ReferenceError가 아닌 TypeError가 발생하는 이유는,
add 함수는 호이스팅 시, var로 동작하기 때문에 add는 undefined로만 초기화된 상태에서 함수 형태로 호출해버렸기 때문이다.
글 잘 읽었습니다. 조민호이스팅이네요