본 포스트는 [10분 테코톡] 💙 하루의 실행 컨텍스트 - YouTube 영상을 참고 및 요약하여 만들어졌습니다. 포스트 대신 위의 영상을 보시면 내용을 더 풍부하게 이해하실 수 있습니다.
Lexical Environment의 구성 요소 중 하나인 Environment Record를 알아보면서 함께 호이스팅에 대해서도 알아보고자 한다.
Hoisiting (호이스팅)
자바스크립트 엔진에 의해 코드에서 선언문이 마치 최상단으로 끌어올려진 듯한 현상
Environment Record와 호이스팅에 대해 이해 하기 전에 잠~깐 자바스크립트 엔진이 코드를 어떻게 실행하는지 알아보고 넘어갈 것 이다.
🧐 JS 엔진의 코드 실행 단계
- 생성 단계(Creation Phase) : 선언문만 우선 실행해서 Environment Record에 기록을 하는 단계
- 실행 단계(Execution Phase) : 선언문 외 다른 코드들도 순차적으로 실행하는 단계. 이때에는 Environment Record를 참조하거나 업데이트 한다.
JS엔진의 실행 단계를 알아보았으니 이제 호이스팅에 대해 이해해 볼 수 있다. 호이스팅은 크게 3가지로 나뉘는데 첫번째는 Variable의 호이스팅, 두번째는 function expression, 세번째는 함수 선언의 호이스팅 이다. 각 호이스팅 별로 JS 엔진의 실행 단계와 함께 Environment Record가 어떻게 바뀌어 나가는지 살펴볼 것이다.
console.log(TVChannel);
const TVChannel = "Netflix";
console.log(TVChannel);
선언문을 제외한 나머지 코드들을 실행한다. TVChannel이란 변수를 출력하려고 시도하지만 Context내의 Record를 확인했을때 TVChannel은 어떠한 값도 띄지 않고 있기에 어떠한 값을 출력할 수 없어서 Reference Error가 발생하게 된다.
위와 같이 const(let)으로 선언했을때 선언하기 이전에 식별자를 참조할 수 없는 구역이 존재하게 되는데 이러한 구역을 일시적 사각지대(Temporal Dead Zone)이라고 한다. 이는 let, const로 선언한 변수의 경우 선언이 되었을 때 초기화는 따로 이루어지기 때문에 발생하게 된다.
Function Expression(함수 표현식)은 함수를 특정 변수에 담아두는 방식을 의미
Function Expression은 함수를 변수에 담아둔 형태이기에 JS엔진에게는 변수로 취급되므로 호이스팅은 변수와 동일한 방식으로 이루어진다. (var 함수표현식은 undefined로 초기화되고, let과 const는 어떤 값으로도 초기화 되지 않는다.)
study();
function study(){
// ...
};
Context 생성 후 Record 내부에 study라는 함수 객체를 생성 후 기록해 둔다. 이 때에는 JS엔진이 함수 선언과 동시에 함수 객체를 생성해 둔다.
JS엔진이 Context내 Record를 참조하여 study 함수를 정상적으로 실행한다.
이처럼 함수 선언의 경우에는 선언과 동시에 함수객체가 생성되기 때문에 호이스팅이 일어나도 정상적으로 실행될 수 있다.
Outer Environment Refernece는 외부 환경을 참조하는 역할을 하며 이전에 실행된 Lexcial Environment를 가리킨다. 이는 식별자 결정 (Identifier Resolution)이 필요한 상황에서 사용된다. 아래의 상황을 생각해보자
다음의 코드를 예시로 들어보면, Call Stack에 전역 Execution Context가 생성된 뒤, goTo2F 함수가 실행되면서 goTo2F 함수에 대한 Execution Context가 생성된 것을 볼 수 있다. 이 상황에서 goTo2F 함수가 lamp 변수를 출력해야 한다면, 어떤 변수를 출력해야할까? 이 상황을 JS엔진은 Outer를 이용하여 해결하려고 한다.
let lamp = false;
function goTo2F() {
let lamp = true;
function goTo3F(){
let pet = 'puppy';
console.log(pet); // puppy
console.log(lamp); // ????
}
goTo3F();
}
goTo2F();
다음의 코드를 예시로 들어 JS엔진이 코드를 실행하면서 Outer Environment Reference를 어떻게 사용하는지 알아보려고 한다.
다음은 goTo2F 함수가 실행되는 시점을 가리킨다. 전역에 선언된 lamp와 goTo2F 함수가 전역 Execution Context 내부에 생성되어 있는 모습이다.
이 상황에서 goTo2F 함수를 실행하면 goTo2F 함수에대한 Execution Context를 생성한 뒤 새로 생성된 Exeuction Context의 Outer에는 전역 Lexcial Environment(Record + Outer)로 되돌아갈 수 있는 Outer를 생성해 둔다.
위의 그림은 goTo2F 함수가 실행되고 났을때의 상황을 보여준다. lamp 변수와 goTo3F 함수 객체가 Record에 기록된다. 이후에는 goTo3F 함수가 실행될 것이다.
위의 그림은 goTo3F 함수가 실행되는 시점을 나타낸다. 이전과 마찬가지로 goTo3F라는 새로운 함수가 실행되었으므로 새로운 Exeuction Environment를 생성하고 여기에는 goTo2F 함수의 Execution Environment로 돌아갈 수 있는 outer를 생성해 놓은 모습이다.
위 그림은 goTo3F가 본격적으로 실행되고 났을 때의 모습을 나타낸다. Record에 변수 pet이 기록된 모습이다. 이 상황에서 pet의 값을 출력해야 한다면, 현재 실행중인 Context(그림 상 제일 위에 위치한 Context)를 먼저 참조하여 pet의 값을 출력해낸다.
이외에 corona라는 변수를 출력해야 한다면 어떻게 될까? JS 엔진은 마찬가지로 현재 실행중인 Execution Context 부터 살펴보며 corona가 어떤 값을 가지는지 찾아본다. 여기서 현재 찾아보고 있는 Context 안에 corona 변수 값이 존재하지 않는다면 outer를 통해 이전 Execution Context로 이동하여 탐색과정을 진행한다. 이러한 과정을 전역 Exeuction Context에 도달할 때 까지 반복해 낸다. 결국 찾아내지 못한다면, corona 변수는 어떤 값인지 알 수가 없기에 Reference Error를 뿜어낸다.
다음은 lamp라는 변수의 값을 출력해야 되는 경우이다. 이 경우도 마찬가지로 Execution Context들을 찾아보다가 가운데 Execution Context에서 lamp의 값을 발견하게 되고 이 때에는 탐색 과정을 종료하게 된다. 한편, 이러한 경우 동일한 식별자인 전역 Execution Context에 위치한 lamp의 값은 알 수가 없게 되는데 이러한 상황을 변수 섀도잉(Variable Shadowing) 이라고 한다.
변수 섀도잉(Variable Shadowing)
동일한 식별자로 인하여 상위 스코프에서 선언된 식별자의 값이 가려지는 현상
이전에 선언된 코드의 Context들을 정리해보면 다음과 같다. 위에서 언급했듯 JS 엔진은 코드 실행 시 식별자의 값을 찾아내기 위해 Call Stack 내부의 선언된 Execution Context들을 Outer를 통해 둘러보는 과정을 진행한다. 이처럼 식별자의 값을 결정할 때 사용하는 Context 또는 스코프들의 연결 리스트를 스코프 체인이라고 하고, 식별자를 결정할 때 스코프를 이동하는 과정 자체를 스코프 체이닝 이라고 한다.
지금까지 Execution Context내 Lexcial Environment를 구성하는 Lexical Environment Record와 Outer Environment Reference의 의미와 그 기능에 대해 알아보았다. Execution Context의 기능을 한마디로 요약한다면 다음과 같다.
Exeuction Context(실행 컨텍스트)
코드를 실행하는데 필요한 환경을 제공하는 객체
여기서 환경이란 코드 실행에 영향을 주는 조건이나 상태를 의미한다.
이처럼 자바스크립트가 Execution Context라는 객체를 통해 식별자를 판별할 수 있는 것은 ES5이후 부터였다.
이전에는 함수가 어디에서 호출되느냐에 따라 스코프가 결정되는 동적 스코프 방식이였다면 ES5 이후로는 Execution Context를 이용해 함수가 어디에 선언되어있는지에 따라 스코프가 결정되는 정적 스코프 방식을 채택하게 되었다. 이를 통해 자바스크립트는 좀 더 효율적으로 식별자의 값을 결정해낼 수 있게 되었다.