자바스크립트의 핵심 개념과 동작 원리를 이해하기 코어 자바스크립트를 읽고 학습한 내용을 정리합니다.
01 실행 컨텍스트란?
- 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념입니다.
스택
- 스택은 출입구가 하나뿐인 깊은 우물 같은 데이터 구조입니다.
- 비어있는 스택에 순서대로 데이터 a, b, c, d를 저장했다며느 꺼낼 때는 반대로 d, c, b, a의 순서대로 꺼낼 수밖에 없습니다.
- 데이터를 100만개만 저장할 수 있는 우물에 100만개 이상의 데이터를 넣으려고 하면 넘치기 때문에 스택이 넘치면 에러를 던집니다.
큐
- 큐는 양쪽이 모두 열려있는 파이프를 떠올리면 됩니다.
- 종류에 따라 양쪽 모두 입력과 출력이 가능한 큐도 있으나 보통은 한쪽은 입력만, 다른 한쪽은 출력만을 담당하는 구조를 말합니다.
- 이 경우 비어있는 큐에 순서대로 데이터 a, b, c, d를 저장했다면 꺼낼 때도 역시 a, b, c, d의 순서로 꺼낼 수밖에 없습니다.
실행 컨텍스트와 콜 스택 예제
var a = 1;
function outer() {
function inner() {
console.log(a);
var a = 3;
}
inner();
console.log(a);
}
outer();
console.log(a);
예제와 같이 처음 자바스크립트 코드를 실행하는 순간(1) 전역 컨텍스트가 콜 스택에 담깁니다.
전역 컨텍스트라는 개념은 일반적인 실행 컨텍스트와 특별히 다를 것이 없이, 최상단의 공간은 코드 내부에서 별도의 실행 명령이 없어도 브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 이해하면 됩니다.
다음으로 전역 컨텍스트와 관련된 코드들을 순차로 진행하다가 (3)에서 outer 함수를 호출하면 자바스크립트 엔진은 outer에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 생성ㅇ한 후 콜 스택에 담습니다. 콜 스택의 맨 위에 outer 실행 컨텍스트가 놓인 상태가 됐으므로 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 대신 outer 실행 컨텍스트와 관련된 코드, 즉 outer 함수 내부의 코드들을 순차로 실행합니다.
다시 (2)에서 inner 함수의 실행 컨텍스트가 콜 스택의 가장 위에 담기면 outer 컨텍스트와 관련된 코드의 실행을 중단하고 inner 함수 내부의 코드를 순서대로 진행할 것입니다.
inner 함수 내부에서 a 변수에 값 3을 할당하고 나면 inner 함수의 실행이 종료되면서 inner 실행 컨텍스트가 콜 스택에서 제거됩니다. 그러면 아래에 있던 outer 컨텍스트가 콜 스택의 맨 위에 존재하게 되므로 중단했던 (2)의 다음 줄부터 이어서 실행합니다. a 변수의 값을 출력하고 나면 outer 함수의 실행이 종료되어 outer 실행 컨텍스트가 콜 스택에서 제거되고, 콜 스택에는 전역 컨텍스트만 남아 있게 됩니다. 그런 다음, 실행을 중단했던 (3)의 다음 줄부터 이어서 실행합니다. a 변수의 값을 출력하고 나면 전역 공간에 더는 실행할 코드가 남아 있지 않아 전역 컨텍스트도 제거되고, 콜 스택에는 아무것도 남지 않은 상태로 종료됩니다.
스택 구조를 잘 생각해보면 한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점임을 할 수 있습니다. 기존의 컨텍스트는 새로 쌓인 컨텍스트보다 아래에 위치할 수밖에 없기 때문이죠. 이렇게 어떤 실행 컨텍스트가 활성화될 때 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장합니다. 이 객체는 자바스크립트 엔진이 활용할 목적으로 생성할 뿐 개발자가 코드를 통해 확인할 수 는 없습니다. 여기에 담기는 정보들은 다음과 같습니다.
- VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보
- LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨.
- ThisBinding: this 식별자가 바라봐야 할 대상 객체
02 VariableEnvironment
- VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다릅니다. 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용하게 됩니다.
- VariableEnvironment와 LexicalEnvironment의 내부는 environmentRecord와 outerEnvironment로 구성돼 있습니다. 초기화 과정 중에는 사실상 완전히 동일하고 이후 코드 진행에 따라 서로 달라지게 될 것이므로 자세한 내용은 LexicalEnvironment를 통해 함께 살펴보겠습니다.
03 LexicalEnvironment
- 사전적 환경이라고 생각하면 이해하기 쉽습니다.
- 현재 컨텍스트 내부에는 a, b, c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성돼있다 라는, 컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것입니다.
environmentRecord와 호이스팅
- environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다.
- 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등이 식별자에 해당합니다.
- 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집합니다.
변수 정보를 수집하는 과정을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태입니다. 코드가 실행되기 전임에도 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 되는 셈이죠. 그렇다면 엔진의 실제동작 방식 대신에 '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다'라고 생각하더라도 코드를 해석하는 데는 문제될 것이 전혀 없을 것입니다. 여기서 호이스팅이라는 개념이 등장합니다.
호이스팅이란 끌어올리다라는 의미의 hoist에 ing를 붙여 만든 동명사로, 변수정보를 수집하는 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념입니다. 자바스크립트 엔진이 실제로 끌어올리지는 않지만 편의상 끌어올린 것으로 간주하자는 것이죠.
즉 호이스팅은 코드 해석을 좀 더 수월하게 하기 위해 environmentRecord의 수집 과정을 추상화한 개념으로, 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 끌어올린다고 해석하는 것입니다.
environmentRecord는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집합니다.
04 스코프, 스코프 체인, outerEnvironmentReference
- 스코프란 식별자에 대한 유효범위입니다.
어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐 아니라 A의 내부에서도 접근이 가능하지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있습니다.
- 식별자의 유효범위를 안에서부터 바깥쪽으로 차례로 검색해나가는 것을 스코프 체인이라고 합니다.
- 이를 가능케 하는 것이 바로 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference 입니다.
정리
- 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.
- 실행 컨텍스트 객체는 활성화되는 시점에 VariableEnvironment, LexicalEnvironment, ThisBinding의 세 가지 정보를 수집합니다.
- 실행 컨텍스트를 생성할 때는 VariableEnvironment와 LexicalEnvironment가 동일한 내용으로 구성되지만 LexicalEnvironment는 함수 실행 도중에 변경되는 사항이 즉시 반영되는 반면 VariableEnvironment는 초기 상태를 유지합니다.
- 이 둘은 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는 environmentRecord와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성돼 있습니다.