이 글은 Core Javascript를 읽고 내용을 정리하였습니다. 2회독 하였지만 잘못된 내용의 지적을 감사히 받겠습니다.
실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로 자바스크립트 동작 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다. 자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보를 구성하고, this값을 설정하는 등의 동작을 수행한다.
스택과 큐를 그림으로 보면 이해가 쉽다. 스택은 비어있는 스택에 abcd순서대로 저장했다면 dcba순서대로 실행이 되는 환경이고, 큐는 들어간 순서대로 abcd => abcd 순서대로 실행되는 구조이다. 앞서 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체라고 설명했는데, 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다. 우리가 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것 뿐이다.
다음과 같은 코드를 보자
// --------------------------------------------(1)
let a = 1;
function outer(){
function inner(){
console.log(a); // undefined
let a =3;
}
inner(); //-------------------------------(2)
console.log(a); // 1
}
outer(); //-----------------------------------(3)
console.log(a) // 1
다음과 같은 코드를 처음 자바스크립트를 실행 하는 순간 (1) 번의 전역 컨텍스트가 콜 스택에 담기고, 다음으로 (2) => (3) 번의 순서로 콜 스택에 담기게 됩니다. 약도로 보자면
이해를 돕기 위해 그렸습니다ㅜ
그리고 콜 스택에 담긴 역순으로 함수가 실행되게 됩니다.
스택 구조를 잘 샐각해보면 한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점임을 알 수 있다.
활성화된 실행 컨텍스트는 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해 실행 컨텍스트 객체에 저장하게 되는데, 실행 컨텍스트의 구조는 다음과 같습니다.
처음 보는 정보 이름이 나올 수 있으나 당황하지 말고 하나씩 살펴 보도록 하자
VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다르다. 어렵게 생각하지말고 그냥 최초 상태의 정보를 가지고 있는다고 생각하면 된다. 엄연히 말하면 VariableEnvironment에 내용을 담고 이를 토대로 렉시컬환경에 복사한다. 이후에는 주로 렉시컬 환경을 주로 활용하게 된다. 각각의 환경에는 environmentRecord와 outerEnvironmentReference로 구성되어 있다. 다음 렉시컬환경에서 설명하겠다.
Lexical의 번역은 '어휘적'이다. 어휘적이라고 하면 와닿지가 않는데 쉽게 생각해서 사전적인,정의적인 으로 이해하면 쉽다. LexicalEnvironment에서는 초기에는 VariableEnvironment와 같지만 그것을 복사하여 가지고 있다가 변경 사항이 이뤄지면 실시간으로 반영되게 된다. 즉 변화한다.
LexicalEnvironment환경에는 2가지로 구성되어있는데 바로 environmentRecord와 outerEnvironmentReference이다.
environmentRecord는 현재 컨텍스트와 관련된 코드의 식별자 정보들을 저장한다. 예를들면 한 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체와 var 선언된 변수의 식별자 등의 정보가 저장된다. 여기서 변수 정보를 수집하는 과정을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태이다. 그렇기에 코드가 실행되지 않더라도 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 된다. 여기서 JS의 코어적인 속성이 나오는데 바로 호이스팅이다
보통 호이스팅을 설명할 때 호이스팅은 var을 통해 정의된 변수의 선언문을 유효 범위의 최상단으로 끌어올리는 행위를 말한다고 설명하는데 엄밀히 말하면 최상단으로 끌어올리진 않는다. 이해를 돕기위해 편의상 끌어올린다고 표현한다.
MDN에 정의를 첨언하겠다.
호이스팅을 변수 및 함수 선언이 물리적으로 작성한 코드의 상단으로 옮겨지는 것으로 가르치지만, 실제로는 그렇지 않습니다. 변수 및 함수 선언은 컴파일 단계에서 메모리에 저장되지만, 코드에서 입력한 위치와 정확히 일치한 곳에 있습니다.
environmentRecord는 정보를 수집할 때 코드 내에 식별자들의 정보만 수집한다. 식별자에 어떤 값이 할당될 것인지는 관심이 없기 때문에 변수명의 정보만 수집하고 메모리를 확보하여 주솟값을 연결할 뿐이다.
outerEnvironmentReference는 렉시컬환경에서 두 번째 수집 자료이다.
outerEnvironmentReference는 스코프체인을 위해 참조하는 자료인데 여기서 또 코어적인 속성 스코프체인이 등장한다.
function a(){
let a = 1; (A)
function b(){
console.log(a); (B)
}
b();
}
a();
위 코드라인 (B)를 보면 b() 함수내에 선언하지 않은 a 변수에 접근하여 로그를 찍으력 한다.
로그를 찍으면 무슨 값이 나올까. 바로 1이 찍히게 되는데 왜 b()함수에서 선언하지도 않았던 외부변수 a값을 가져올 수 있을까? 바로 스코프 체인 때문이다.
예를들어 A함수 내부에 B함수를 선언하고 다시 B함수에 C함수를 선언한 경우, 함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조한다. 함수 B에 있는 outerEnvironmentReference는 다시 A의 LexicalEnvironment를 참조하게 된다.이 처럼 outerEnvironmentReference는 체인처럼 연결고리를 띄게된다. 이 체인을 끝까지 찾아 올라가면 결국엔 전역 LexicalEnvironment를 참조하게 된다. 그러나 outerEnvironmentReference는 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로만 접근할 수 있기에 스코프 체인상에서 가장 먼저 발견된 식별자에만 접근이 가능하게 된다.