View
실행 컨텍스트란?
실행할 코드에 제공할 환경 정보들을 모아놓은 객체. 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여 있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다.
실행 컨텍스트에 담기는 정보들
VariableEnvironment
현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음.
LexicalEnvironment
처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨
ThisBinding
식별자가 바라봐야 할 대상 객체.
+) VariableEnvironment와 LexicalEnvironment의 차이를 두는 이유는 무엇일까?
공식 문서에는 변경 사항이 반영되어 Variable과 Lexical이 달라졌을 때, 다시 되돌리고 싶다면 Variable을 사용한다고 적혀 있습니다.
정확이 실행 컨텍스트에 어떤 변경 사항이 생길 수 있는지는 조금 더 공부가 필요할 것 같습니다.
LexicalEnvironment
LexicalEnvironment에는 크게 두 가지 정보가 담깁니다. 바로 environmentRecord와 outerEnvironmentReference입니다.
environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다. 매개변수 식별자, 함수 식별자, 변수 식별자 등이 해당됩니다. 컨텍스트 내부를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집합니다.
여기서 호이스팅이라는 자바스크립트의 아주 중요한 개념이 등장하는데, 조금 뒤에 자세히 알아보도록 하겠습니다.
outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조합니다.
우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것인데, 함수가 실행될 때, 즉 실행 컨텍스트가 활성화되었을 때의 Lexical을 참조하게 되는 것입니다. 이것은 스코프라는 개념과 연결되는데, 호이스팅을 살펴보고 나서 자세히 알아보도록 하겠습니다.
Hoisting
앞서 environmentRecord에는 현재 컨텍스트의 식별자 정보가 저장된다고 했습니다. 그 말은 함수가 실행될 때, 실행 컨텍스트를 구성할 때, 아직 함수는 실행되지 않았지만 그 찰나의 순간에 식별자 정보들은 이미 수집된다는 말입니다.
이 말은 곧 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다는 뜻과 유사합니다. 실제 자바스크립트 엔진이 그렇게 코드를 바꾸거나 하진 않지만 비슷한 개념입니다.
만약 다음과 같은 코드가 있다고 합시다.
function a {
var x = 1;
console.log(x);
var x;
console.log(x);
var x = 2;
consoel.log(x);
}
a();
우리가 흔히 생각하는 위에서 아래로부터 쭉 코드가 실행되는 형태처럼 해당 함수가 실행된다고 하면, 첫 번째 console.log에는 1이 찍힐 거고, 두 번째 console.log에는 undefined가 찍힐 거고, 세 번째 console.log에는 2가 찍힐 것 같습니다. 하지만 실제로는 1, 1, 2가 찍히게 됩니다. 호이스팅이 일어나기 때문입니다.
미리 식별자들을 수집해 놓은 상태이기 때문에 식별자 선언 단계는 무조건 먼저, 할당은 우리가 익숙한 코드 순서대로 이루어지는 것입니다. 이렇게 이해하면 편하겠죠.
function a {
var x;
var x;
var x;
x = 1;
console.log(x);
console.log(x);
x = 2;
consoel.log(x);
}
a();
실제로 일어나는 일은 아니지만, 실행 컨텍스트는 해당 코드의 식별자들을 미리 수집하기 때문에 이런 식으로 코드가 실행이 되는 것입니다.
여기서 함수도 과연 호이스팅이 이루어질까? 하는 의문이 생깁니다.
우리가 함수를 만드는 방법은 함수 선언문, 함수 표현식이 있습니다. 함수 선언문은 일반적인 function a() { /* ... */}의 형식이고, 함수 표현식은 const b = function () {/*... */}와 같은 형태입니다.
실행 컨텍스트의 environmentRecord는 식별자를 모두 수집한다고 했는데, 함수의 이름 또한 식별자가 되겠죠. 함수 선언문은 식별자, 즉 함수명 자체가 식별자가 되기 때문에 호이스팅이 됩니다.
반면에 함수 표현식은 식별자는 b이고 함수는 그 식별자에 할당되는 것이기 때문에 식별자 b만 호이스팅되고, 함수 내용 자체는 호이스팅 되지 않을 것입니다. 마치 앞서 봤던 예에서 변수 선언부만 호이스팅되고, 할당되는 내용은 호이스팅되지 않는 것처럼 말입니다.
따라서 함수 선언문보다 표현식을 쓰는 게 좋습니다. 선언문은 모두 호이스팅되기 때문에 만약 50번째 줄에 a라는 이름의 함수를 선언하고 1000번째 줄에 똑같이 a라는 이름의 함수를 선언하면, 동일한 실행 컨텍스트 내에서 a라는 함수를 사용할 때 1000번째에 선언된 a 함수가 실행되겠죠. 이는 위에서부터 아래로 코드 흐름을 읽어나가는 입장에서 굉장히 불편합니다.
+ ES6에서는 const와 let이 등장했는데, var와 마찬가지로 호이스팅이 되지만 초기화(할당)가 되기 전에 사용하려고 하면 var는 undefined를 띄우는 반면 const와 let은 ReferenceError를 띄우게 됩니다.
Scope
스코프란 식별자에 대한 유효범위입니다. 앞서 말했던 outerEnvironmentReference의 개념과 함께 이해할 수 있습니다.
다음과 같은 예가 있습니다.
var a = 1;
var outer = function() {
var inner = function() {
console.log(a);
var a = 3;
}
inner();
console.log(a);
}
outer();
console.log(a);
이 코드가 실행되는 프로세스를 알아봅시다. 맨 처음, 전역 컨텍스트가 활성화될 것입니다. 전역 컨텍스트의 environmentRecord에는 식별자 정보가 저장되므로 a와 outer가 저장될 것입니다. 그럼 outerEnvironmentReference에는 뭐가 저장될까요? 전역 컨텍스트는 가장 밖(?)에 있기 때문에 아무것도 저장되지 않습니다.
outer()를 통해 outer가 호출됩니다. outer 실행 컨텍스트의 environmentRecord에는 inner가 저장됩니다. outerEnvironmentReference에는 outer가 실행되는 시점의 LexicalEnvironment, 즉 전역 컨텍스트의 lexical~이 저장됩니다.
마찬가지로 inner()를 통해 inner가 호출되면 inner 실행 컨텍스트의 environmentRecord에는 a가 저장됩니다. outerEnvironmentReference에는 outer 함수의 LexicalEnvironment가 저장될 것입니다.
이것이 바로 스코프 체인입니다. 함수 내부에 함수를 선언하고 그 함수에 다시 함수를 선언하는 경우 outerEnvironmentReference를 통해 연결리스트의 형태를 띄게 됩니다. 따라서 식별자를 사용할 때 스코프 체인을 따라 가장 먼저 발견되는 식별자의 정보를 가지고 와 사용하게 됩니다. 다음과 같이 이루어집니다.
- inner 함수 안의 console.log(a);에서 a는 inner 함수 안에서 호이스팅되어 저장되어 있습니다. 하지만 할당은 이후에 되기 때문에 undefined입니다.
- 만약 inner 함수 내에서 a를 선언하지 않았다면 1이 출력될 것입니다. inner의 environmentRecord에는 a가 없는 상태기 때문에 outerEnvironmentReference를 살펴볼 것이고, 그곳에는 outer의 Lexical이 저장되어 있습니다. 하지만 outer의 environmentRecord 또한 a가 없기 때문에 outer의 outerEnvironmentReference인 전역 Lexical을 살펴봅니다. 그곳에 a가 있네요.
- outer 함수 안의 console.log(a); 에서 a는 현재 outer 함수 안에 저장되어 있지 않으므로 (environmentRecord에 없으므로) outerEnvironmentReference에서 한번 찾아보게 됩니다. outer의 outerEnvironmentReference는 전역 컨텍스트의 lexical~이었죠? 전역 컨텍스트의 environmentRecord에 a가 있습니다. 1이 출력됩니다.
- 마지막 전역의 console.log(a);에서는 전역 environmentRecord에 a가 있으므로 1이 출력됩니다.
여기에서 변수 은닉화라는 개념도 등장합니다. inner 함수에서 a를 선언했기 때문에 전역에 있는 a에는 접근할 수 없게 됩니다. 이를 변수 은닉화라고 합니다.
전역 컨텍스트의 LexicalEnvironment에 저장된 변수들을 전역변수라고 하고, 나머지를 다 지역변수라고 합니다.
'Level-Up > 독서' 카테고리의 다른 글
[코어 자바스크립트] 05. 클로저 (0) | 2022.04.05 |
---|---|
[코어 자바스크립트] 04. 콜백 함수 (0) | 2022.03.25 |
[코어 자바스크립트] 3장. this (0) | 2022.03.11 |
[코어 자바스크립트] 1장. 데이터 타입 (0) | 2022.02.21 |